mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-17 21:38:22 +01:00
migrate TypeInferers to PHPStan object types
This commit is contained in:
parent
7249858618
commit
622b6c1460
@ -69,7 +69,6 @@
|
||||
"Rector\\PHPUnitSymfony\\": "packages/PHPUnitSymfony/src",
|
||||
"Rector\\PHPUnit\\": "packages/PHPUnit/src",
|
||||
"Rector\\PSR4\\": "packages/PSR4/src",
|
||||
"Rector\\PhpParser\\": "packages/PhpParser/src",
|
||||
"Rector\\PhpSpecToPHPUnit\\": "packages/PhpSpecToPHPUnit/src",
|
||||
"Rector\\Php\\": "packages/Php/src",
|
||||
"Rector\\RemovingStatic\\": "packages/RemovingStatic/src",
|
||||
@ -114,7 +113,6 @@
|
||||
"Rector\\PHPStan\\Tests\\": "packages/PHPStan/tests",
|
||||
"Rector\\PHPUnitSymfony\\Tests\\": "packages/PHPUnitSymfony/tests",
|
||||
"Rector\\PHPUnit\\Tests\\": "packages/PHPUnit/tests",
|
||||
"Rector\\PhpParser\\Tests\\": "packages/PhpParser/tests",
|
||||
"Rector\\PhpSpecToPHPUnit\\Tests\\": "packages/PhpSpecToPHPUnit/tests",
|
||||
"Rector\\Php\\Tests\\": "packages/Php/tests",
|
||||
"Rector\\RemovingStatic\\Tests\\": "packages/RemovingStatic/tests",
|
||||
@ -140,7 +138,15 @@
|
||||
"tests/Rector/Namespace_/PseudoNamespaceToNamespaceRector/Source"
|
||||
],
|
||||
"files": [
|
||||
"packages/DeadCode/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/Source/UserDefined.php"
|
||||
"packages/DeadCode/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/Source/UserDefined.php",
|
||||
"packages/Php/tests/Rector/Property/CompleteVarDocTypePropertyRector/Source/EventDispatcher.php",
|
||||
"tests/Rector/Namespace_/PseudoNamespaceToNamespaceRector/Source/ChangeMeAnotherNamespace.php",
|
||||
"tests/Issues/Issue1243/Source/Twig_Environment.php",
|
||||
"tests/Issues/Issue1243/Source/Twig_Error_Loader.php",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
|
3
ecs.yaml
3
ecs.yaml
@ -46,6 +46,7 @@ services:
|
||||
Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer:
|
||||
extra_skipped_classes:
|
||||
- 'PhpParser\PrettyPrinter\Standard'
|
||||
- '?string' # bug probably
|
||||
|
||||
Symplify\CodingStandard\Sniffs\Naming\ClassNameSuffixByParentSniff:
|
||||
extra_parent_types_to_suffixes:
|
||||
@ -111,6 +112,7 @@ parameters:
|
||||
- 'src/Rector/AbstractRector.php'
|
||||
|
||||
Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff:
|
||||
- 'packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php'
|
||||
- 'packages/NodeTypeResolver/src/NodeTypeResolver.php'
|
||||
- 'packages/NodeTypeResolver/src/PerNodeTypeResolver/VariableTypeResolver.php'
|
||||
- 'packages/Php/src/Rector/FuncCall/RemoveExtraParametersRector.php'
|
||||
@ -176,6 +178,7 @@ parameters:
|
||||
- 'src/PhpParser/Node/Value/ValueResolver.php'
|
||||
|
||||
PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer:
|
||||
- 'packages/BetterPhpDocParser/tests/PhpDocInfo/PhpDocInfo/PhpDocInfoTest.php'
|
||||
# intentional "assertEquals()"
|
||||
- 'tests/PhpParser/Node/NodeFactoryTest.php'
|
||||
- '*TypeResolverTest.php'
|
||||
|
@ -5,7 +5,7 @@ services:
|
||||
|
||||
Rector\BetterPhpDocParser\:
|
||||
resource: '../src'
|
||||
exclude: '../src/{HttpKernel,Data,*/*Info.php,*Info.php,Attributes/Ast/PhpDoc/*}'
|
||||
exclude: '../src/{HttpKernel,Data,*/*Info.php,*Info.php,Attributes/Ast/PhpDoc/*,Ast/PhpDoc/*}'
|
||||
|
||||
PHPStan\PhpDocParser\Lexer\Lexer: ~
|
||||
PHPStan\PhpDocParser\Parser\TypeParser: ~
|
||||
|
@ -55,7 +55,7 @@ final class NodeTraverser
|
||||
{
|
||||
$typeNode = $callable($typeNode);
|
||||
|
||||
if ($typeNode instanceof ArrayTypeNode) {
|
||||
if ($typeNode instanceof ArrayTypeNode || $typeNode instanceof NullableTypeNode) {
|
||||
$typeNode->type = $this->traverseTypeNode($typeNode->type, $callable);
|
||||
}
|
||||
|
||||
@ -65,10 +65,6 @@ final class NodeTraverser
|
||||
}
|
||||
}
|
||||
|
||||
if ($typeNode instanceof NullableTypeNode) {
|
||||
$typeNode->type = $this->traverseTypeNode($typeNode->type, $callable);
|
||||
}
|
||||
|
||||
return $typeNode;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,71 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\BetterPhpDocParser\Ast\PhpDoc\JMS;
|
||||
|
||||
use JMS\DiExtraBundle\Annotation\Inject;
|
||||
use Rector\BetterPhpDocParser\PhpDocParser\Ast\PhpDoc\AbstractTagValueNode;
|
||||
|
||||
final class JMSInjectTagValueNode extends AbstractTagValueNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const SHORT_NAME = '@DI\Inject';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const CLASS_NAME = Inject::class;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $serviceName;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private $required;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $strict = false;
|
||||
|
||||
public function __construct(?string $serviceName, ?bool $required, bool $strict)
|
||||
{
|
||||
$this->serviceName = $serviceName;
|
||||
$this->required = $required;
|
||||
$this->strict = $strict;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$itemContents = [];
|
||||
|
||||
if ($this->serviceName) {
|
||||
$itemContents[] = $this->serviceName;
|
||||
}
|
||||
|
||||
if ($this->required !== null) {
|
||||
$itemContents[] = sprintf('required=%s', $this->required ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if ($this->strict !== null) {
|
||||
$itemContents[] = sprintf('strict=%s', $this->strict ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if ($itemContents === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$stringContent = implode(', ', $itemContents);
|
||||
|
||||
return '(' . $stringContent . ')';
|
||||
}
|
||||
|
||||
public function getServiceName(): ?string
|
||||
{
|
||||
return $this->serviceName;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\BetterPhpDocParser\Ast\PhpDoc\PHPDI;
|
||||
|
||||
use DI\Annotation\Inject;
|
||||
use Rector\BetterPhpDocParser\PhpDocParser\Ast\PhpDoc\AbstractTagValueNode;
|
||||
|
||||
final class PHPDIInjectTagValueNode extends AbstractTagValueNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const SHORT_NAME = '@Inject';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const CLASS_NAME = Inject::class;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
public function __construct(?string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '(' . $this->value . ')';
|
||||
}
|
||||
}
|
@ -14,36 +14,11 @@ final class Attribute
|
||||
*/
|
||||
public const PHP_DOC_NODE_INFO = 'php_doc_node_info';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_AS_STRING = 'type_as_string';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_AS_ARRAY = 'type_as_array';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const LAST_TOKEN_POSITION = 'last_token_position';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const RESOLVED_NAME = 'resolved_name';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const RESOLVED_NAMES = 'resolved_names';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const ANNOTATION_CLASS = 'annotation_class';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\BetterPhpDocParser\Extension;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
||||
use PHPStan\PhpDocParser\Parser\TokenIterator;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocParserExtensionInterface;
|
||||
use Rector\BetterPhpDocParser\PhpDocParser\InjectPhpDocParser;
|
||||
|
||||
final class JMSPhpDocParserExtension implements PhpDocParserExtensionInterface
|
||||
{
|
||||
/**
|
||||
* @var InjectPhpDocParser
|
||||
*/
|
||||
private $injectPhpDocParser;
|
||||
|
||||
public function __construct(InjectPhpDocParser $injectPhpDocParser)
|
||||
{
|
||||
$this->injectPhpDocParser = $injectPhpDocParser;
|
||||
}
|
||||
|
||||
public function matchTag(string $tag): bool
|
||||
{
|
||||
if ($tag === '@Inject') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) Strings::match($tag, '#^@DI\\\\(\w+)$#');
|
||||
}
|
||||
|
||||
public function parse(TokenIterator $tokenIterator, string $tag): ?PhpDocTagValueNode
|
||||
{
|
||||
return $this->injectPhpDocParser->parse($tokenIterator, $tag);
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\BetterPhpDocParser\NodeDecorator;
|
||||
|
||||
use PhpParser\Node as PhpParserNode;
|
||||
use PHPStan\PhpDocParser\Ast\Node;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use Rector\BetterPhpDocParser\Ast\NodeTraverser;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute;
|
||||
use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterface;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocNodeDecoratorInterface;
|
||||
|
||||
final class StringsTypePhpDocNodeDecorator implements PhpDocNodeDecoratorInterface
|
||||
{
|
||||
/**
|
||||
* @var NodeTraverser
|
||||
*/
|
||||
private $nodeTraverser;
|
||||
|
||||
public function __construct(NodeTraverser $nodeTraverser)
|
||||
{
|
||||
$this->nodeTraverser = $nodeTraverser;
|
||||
}
|
||||
|
||||
public function decorate(
|
||||
AttributeAwarePhpDocNode $attributeAwarePhpDocNode,
|
||||
PhpParserNode $phpParserNode
|
||||
): AttributeAwarePhpDocNode {
|
||||
$this->nodeTraverser->traverseWithCallable(
|
||||
$attributeAwarePhpDocNode,
|
||||
function (AttributeAwareNodeInterface $phpParserNode): AttributeAwareNodeInterface {
|
||||
$typeNode = $this->resolveTypeNode($phpParserNode);
|
||||
if ($typeNode === null) {
|
||||
return $phpParserNode;
|
||||
}
|
||||
|
||||
$typeAsString = (string) $typeNode;
|
||||
$typeAsArray = explode('|', $typeAsString);
|
||||
|
||||
$phpParserNode->setAttribute(Attribute::TYPE_AS_ARRAY, $typeAsArray);
|
||||
$phpParserNode->setAttribute(Attribute::TYPE_AS_STRING, $typeAsString);
|
||||
|
||||
return $phpParserNode;
|
||||
}
|
||||
);
|
||||
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
|
||||
private function resolveTypeNode(Node $node): ?TypeNode
|
||||
{
|
||||
if ($node instanceof ParamTagValueNode || $node instanceof VarTagValueNode || $node instanceof ReturnTagValueNode) {
|
||||
return $node->type;
|
||||
}
|
||||
|
||||
if ($node instanceof TypeNode) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -3,17 +3,19 @@
|
||||
namespace Rector\BetterPhpDocParser\PhpDocInfo;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\BetterPhpDocParser\Annotation\AnnotationNaming;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareParamTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareReturnTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareVarTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute;
|
||||
use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterface;
|
||||
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
|
||||
/**
|
||||
* @see \Rector\BetterPhpDocParser\Tests\PhpDocInfo\PhpDocInfo\PhpDocInfoTest
|
||||
@ -40,18 +42,32 @@ final class PhpDocInfo
|
||||
*/
|
||||
private $originalPhpDocNode;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var Node
|
||||
*/
|
||||
private $node;
|
||||
|
||||
/**
|
||||
* @param mixed[] $tokens
|
||||
*/
|
||||
public function __construct(
|
||||
AttributeAwarePhpDocNode $attributeAwarePhpDocNode,
|
||||
array $tokens,
|
||||
string $originalContent
|
||||
string $originalContent,
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
Node $node
|
||||
) {
|
||||
$this->phpDocNode = $attributeAwarePhpDocNode;
|
||||
$this->tokens = $tokens;
|
||||
$this->originalPhpDocNode = clone $attributeAwarePhpDocNode;
|
||||
$this->originalContent = $originalContent;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->node = $node;
|
||||
}
|
||||
|
||||
public function getOriginalContent(): string
|
||||
@ -95,11 +111,6 @@ final class PhpDocInfo
|
||||
return $this->getPhpDocNode()->getParamTagValues();
|
||||
}
|
||||
|
||||
public function hasTag(string $name): bool
|
||||
{
|
||||
return (bool) $this->getTagsByName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PhpDocTagNode[]
|
||||
*/
|
||||
@ -111,17 +122,7 @@ final class PhpDocInfo
|
||||
$tags = $this->phpDocNode->getTags();
|
||||
|
||||
$tags = array_filter($tags, function (PhpDocTagNode $tag) use ($name): bool {
|
||||
if ($tag->name === $name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var PhpDocTagNode|AttributeAwareNodeInterface $tag */
|
||||
$annotationClass = $tag->getAttribute(Attribute::ANNOTATION_CLASS);
|
||||
if ($annotationClass === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return AnnotationNaming::normalizeName($annotationClass) === $name;
|
||||
return $tag->name === $name;
|
||||
});
|
||||
|
||||
return array_values($tags);
|
||||
@ -143,76 +144,34 @@ final class PhpDocInfo
|
||||
return null;
|
||||
}
|
||||
|
||||
// types
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getParamTypes(string $name): array
|
||||
public function getParamType(string $name): Type
|
||||
{
|
||||
$paramTagValue = $this->getParamTagValueByName($name);
|
||||
if ($paramTagValue === null) {
|
||||
return [];
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return $this->getResolvedTypesAttribute($paramTagValue);
|
||||
return $this->staticTypeMapper->mapPHPStanPhpDocTypeToPHPStanType($paramTagValue, $this->node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getVarTypes(): array
|
||||
public function getVarType(): Type
|
||||
{
|
||||
$varTagValue = $this->getVarTagValue();
|
||||
if ($varTagValue === null) {
|
||||
return [];
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return $this->getResolvedTypesAttribute($varTagValue);
|
||||
return $this->staticTypeMapper->mapPHPStanPhpDocTypeToPHPStanType($varTagValue, $this->node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getShortVarTypes(): array
|
||||
{
|
||||
$varTagValue = $this->getVarTagValue();
|
||||
if ($varTagValue === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $varTagValue->getAttribute(Attribute::TYPE_AS_ARRAY) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getShortReturnTypes(): array
|
||||
public function getReturnType(): Type
|
||||
{
|
||||
$returnTypeValueNode = $this->getReturnTagValue();
|
||||
if ($returnTypeValueNode === null) {
|
||||
return [];
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return $returnTypeValueNode->getAttribute(Attribute::TYPE_AS_ARRAY) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getReturnTypes(): array
|
||||
{
|
||||
$returnTypeValueNode = $this->getReturnTagValue();
|
||||
if ($returnTypeValueNode === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->getResolvedTypesAttribute($returnTypeValueNode);
|
||||
}
|
||||
|
||||
public function getDoctrineRelationTagValueNode(): ?DoctrineRelationTagValueNodeInterface
|
||||
{
|
||||
return $this->getByType(DoctrineRelationTagValueNodeInterface::class);
|
||||
return $this->staticTypeMapper->mapPHPStanPhpDocTypeToPHPStanType($returnTypeValueNode, $this->node);
|
||||
}
|
||||
|
||||
public function removeTagValueNodeFromNode(PhpDocTagValueNode $phpDocTagValueNode): void
|
||||
@ -228,9 +187,6 @@ final class PhpDocInfo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*/
|
||||
public function getByType(string $type): ?PhpDocTagValueNode
|
||||
{
|
||||
foreach ($this->phpDocNode->children as $phpDocChildNode) {
|
||||
@ -257,17 +213,4 @@ final class PhpDocInfo
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpDocTagValueNode|AttributeAwareNodeInterface $phpDocTagValueNode
|
||||
* @return string[]
|
||||
*/
|
||||
private function getResolvedTypesAttribute(PhpDocTagValueNode $phpDocTagValueNode): array
|
||||
{
|
||||
if ($phpDocTagValueNode->getAttribute(Attribute::RESOLVED_NAMES)) {
|
||||
return $phpDocTagValueNode->getAttribute(Attribute::RESOLVED_NAMES);
|
||||
}
|
||||
|
||||
return $phpDocTagValueNode->getAttribute(Attribute::TYPE_AS_ARRAY);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterfac
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocNodeDecoratorInterface;
|
||||
use Rector\BetterPhpDocParser\PhpDocParser\OrmTagParser;
|
||||
use Rector\Configuration\CurrentNodeProvider;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
|
||||
final class PhpDocInfoFactory
|
||||
{
|
||||
@ -35,6 +36,11 @@ final class PhpDocInfoFactory
|
||||
*/
|
||||
private $currentNodeProvider;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @param PhpDocNodeDecoratorInterface[] $phpDocNodeDecoratorInterfacenodeDecorators
|
||||
*/
|
||||
@ -42,12 +48,14 @@ final class PhpDocInfoFactory
|
||||
PhpDocParser $phpDocParser,
|
||||
Lexer $lexer,
|
||||
array $phpDocNodeDecoratorInterfacenodeDecorators,
|
||||
CurrentNodeProvider $currentNodeProvider
|
||||
CurrentNodeProvider $currentNodeProvider,
|
||||
StaticTypeMapper $staticTypeMapper
|
||||
) {
|
||||
$this->phpDocParser = $phpDocParser;
|
||||
$this->lexer = $lexer;
|
||||
$this->phpDocNodeDecoratorInterfaces = $phpDocNodeDecoratorInterfacenodeDecorators;
|
||||
$this->currentNodeProvider = $currentNodeProvider;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
}
|
||||
|
||||
public function createFromNode(Node $node): PhpDocInfo
|
||||
@ -67,7 +75,7 @@ final class PhpDocInfoFactory
|
||||
|
||||
$phpDocNode = $this->setPositionOfLastToken($phpDocNode);
|
||||
|
||||
return new PhpDocInfo($phpDocNode, $tokens, $content);
|
||||
return new PhpDocInfo($phpDocNode, $tokens, $content, $this->staticTypeMapper, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,9 +119,11 @@ final class BetterPhpDocParser extends PhpDocParser
|
||||
$tokenIterator->next();
|
||||
|
||||
// @todo somehow decouple to tag pre-processor
|
||||
if (Strings::match($tag, '#@(ORM|Assert|Serializer)$#')) {
|
||||
$tag .= $tokenIterator->currentTokenValue();
|
||||
$tokenIterator->next();
|
||||
if (Strings::match($tag, '#@(ORM|Assert|Serializer|DI|Inject)$#')) {
|
||||
if ($tag !== '@Inject') {
|
||||
$tag .= $tokenIterator->currentTokenValue();
|
||||
$tokenIterator->next();
|
||||
}
|
||||
}
|
||||
|
||||
$value = $this->parseTagValue($tokenIterator, $tag);
|
||||
@ -201,9 +203,6 @@ final class BetterPhpDocParser extends PhpDocParser
|
||||
return $attributeAwareNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo cache per tokens array hash
|
||||
*/
|
||||
private function getOriginalContentFromTokenIterator(TokenIterator $tokenIterator): string
|
||||
{
|
||||
$originalTokens = $this->privatesAccessor->getPrivateProperty($tokenIterator, 'tokens');
|
||||
|
@ -0,0 +1,66 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\BetterPhpDocParser\PhpDocParser;
|
||||
|
||||
use DI\Annotation\Inject as PHPDIInjectAlias;
|
||||
use JMS\DiExtraBundle\Annotation\Inject;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
||||
use PHPStan\PhpDocParser\Parser\TokenIterator;
|
||||
use Rector\BetterPhpDocParser\Ast\PhpDoc\JMS\JMSInjectTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Ast\PhpDoc\PHPDI\PHPDIInjectTagValueNode;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
|
||||
final class InjectPhpDocParser extends AbstractPhpDocParser
|
||||
{
|
||||
/**
|
||||
* @var NameResolver
|
||||
*/
|
||||
private $nameResolver;
|
||||
|
||||
public function __construct(NameResolver $nameResolver)
|
||||
{
|
||||
$this->nameResolver = $nameResolver;
|
||||
}
|
||||
|
||||
public function parse(TokenIterator $tokenIterator, string $tag): ?PhpDocTagValueNode
|
||||
{
|
||||
/** @var Class_|Property $currentPhpNode */
|
||||
$currentPhpNode = $this->getCurrentPhpNode();
|
||||
|
||||
// needed for proper doc block formatting
|
||||
$this->resolveAnnotationContent($tokenIterator);
|
||||
|
||||
// Property tags
|
||||
if ($currentPhpNode instanceof Property) {
|
||||
if ($tag === '@DI\Inject') {
|
||||
/** @var Inject $inject */
|
||||
$inject = $this->nodeAnnotationReader->readPropertyAnnotation(
|
||||
$currentPhpNode,
|
||||
JMSInjectTagValueNode::CLASS_NAME
|
||||
);
|
||||
|
||||
if ($inject->value === null) {
|
||||
$serviceName = $this->nameResolver->getName($currentPhpNode);
|
||||
} else {
|
||||
$serviceName = $inject->value;
|
||||
}
|
||||
|
||||
return new JMSInjectTagValueNode($serviceName, $inject->required, $inject->strict);
|
||||
}
|
||||
|
||||
if ($tag === '@Inject') {
|
||||
/** @var PHPDIInjectAlias $inject */
|
||||
$inject = $this->nodeAnnotationReader->readPropertyAnnotation(
|
||||
$currentPhpNode,
|
||||
PHPDIInjectTagValueNode::CLASS_NAME
|
||||
);
|
||||
|
||||
return new PHPDIInjectTagValueNode($inject->getName());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
14
packages/BetterPhpDocParser/src/Type/PreSlashStringType.php
Normal file
14
packages/BetterPhpDocParser/src/Type/PreSlashStringType.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\BetterPhpDocParser\Type;
|
||||
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\VerbosityLevel;
|
||||
|
||||
final class PreSlashStringType extends StringType
|
||||
{
|
||||
public function describe(VerbosityLevel $verbosityLevel): string
|
||||
{
|
||||
return '\string';
|
||||
}
|
||||
}
|
@ -7,11 +7,15 @@ use PhpParser\Comment\Doc;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Nop;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\NullType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
|
||||
use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter;
|
||||
use Rector\BetterPhpDocParser\Type\PreSlashStringType;
|
||||
use Rector\HttpKernel\RectorKernel;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\PHPStan\TypeFactoryStaticHelper;
|
||||
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
|
||||
|
||||
final class PhpDocInfoTest extends AbstractKernelTestCase
|
||||
@ -46,14 +50,6 @@ final class PhpDocInfoTest extends AbstractKernelTestCase
|
||||
$this->docBlockManipulator = self::$container->get(DocBlockManipulator::class);
|
||||
}
|
||||
|
||||
public function testHasTag(): void
|
||||
{
|
||||
$this->assertTrue($this->phpDocInfo->hasTag('param'));
|
||||
$this->assertTrue($this->phpDocInfo->hasTag('@throw'));
|
||||
|
||||
$this->assertFalse($this->phpDocInfo->hasTag('random'));
|
||||
}
|
||||
|
||||
public function testGetTagsByName(): void
|
||||
{
|
||||
$paramTags = $this->phpDocInfo->getTagsByName('param');
|
||||
@ -65,34 +61,37 @@ final class PhpDocInfoTest extends AbstractKernelTestCase
|
||||
$typeNode = $this->phpDocInfo->getParamTypeNode('value');
|
||||
$this->assertInstanceOf(TypeNode::class, $typeNode);
|
||||
|
||||
$this->assertSame(
|
||||
['SomeType', 'NoSlash', '\Preslashed', 'null', '\string'],
|
||||
$this->phpDocInfo->getParamTypes('value')
|
||||
);
|
||||
$paramType = $this->phpDocInfo->getParamType('value');
|
||||
|
||||
$expectedUnionType = TypeFactoryStaticHelper::createUnionObjectType([
|
||||
new ObjectType('SomeType'),
|
||||
new ObjectType('NoSlash'),
|
||||
new ObjectType('\Preslashed'),
|
||||
new NullType(),
|
||||
new PreSlashStringType(),
|
||||
]);
|
||||
|
||||
$this->assertEquals($expectedUnionType, $paramType);
|
||||
}
|
||||
|
||||
public function testGetVarTypes(): void
|
||||
public function testGetVarType(): void
|
||||
{
|
||||
$this->assertSame(['SomeType'], $this->phpDocInfo->getVarTypes());
|
||||
$expectedObjectType = new ObjectType('SomeType');
|
||||
$this->assertEquals($expectedObjectType, $this->phpDocInfo->getVarType());
|
||||
}
|
||||
|
||||
public function testReturn(): void
|
||||
public function testGetReturnType(): void
|
||||
{
|
||||
$this->assertSame(['SomeType'], $this->phpDocInfo->getReturnTypes());
|
||||
$expectedObjectType = new ObjectType('SomeType');
|
||||
$this->assertEquals($expectedObjectType, $this->phpDocInfo->getReturnType());
|
||||
}
|
||||
|
||||
public function testReplaceTagByAnother(): void
|
||||
{
|
||||
$phpDocInfo = $this->createPhpDocInfoFromFile(__DIR__ . '/Source/test-tag.txt');
|
||||
|
||||
$this->assertFalse($phpDocInfo->hasTag('flow'));
|
||||
$this->assertTrue($phpDocInfo->hasTag('test'));
|
||||
|
||||
$this->docBlockManipulator->replaceTagByAnother($phpDocInfo->getPhpDocNode(), 'test', 'flow');
|
||||
|
||||
$this->assertFalse($phpDocInfo->hasTag('test'));
|
||||
$this->assertTrue($phpDocInfo->hasTag('flow'));
|
||||
|
||||
$this->assertStringEqualsFile(
|
||||
__DIR__ . '/Source/expected-replaced-tag.txt',
|
||||
$this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo)
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace Rector\CodeQuality\Rector\Class_;
|
||||
|
||||
use Nette\Utils\Arrays;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
@ -11,9 +10,11 @@ use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
@ -22,6 +23,7 @@ use Rector\RectorDefinition\RectorDefinition;
|
||||
* @see https://3v4l.org/GL6II
|
||||
* @see https://3v4l.org/eTrhZ
|
||||
* @see https://3v4l.org/C554W
|
||||
*
|
||||
* @see \Rector\CodeQuality\Tests\Rector\Class_\CompleteDynamicPropertiesRector\CompleteDynamicPropertiesRectorTest
|
||||
*/
|
||||
final class CompleteDynamicPropertiesRector extends AbstractRector
|
||||
@ -31,20 +33,20 @@ final class CompleteDynamicPropertiesRector extends AbstractRector
|
||||
*/
|
||||
private const LARAVEL_COLLECTION_CLASS = 'Illuminate\Support\Collection';
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var DocBlockManipulator
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
public function __construct(StaticTypeMapper $staticTypeMapper, DocBlockManipulator $docBlockManipulator)
|
||||
/**
|
||||
* @var TypeFactory
|
||||
*/
|
||||
private $typeFactory;
|
||||
|
||||
public function __construct(DocBlockManipulator $docBlockManipulator, TypeFactory $typeFactory)
|
||||
{
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->typeFactory = $typeFactory;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
@ -103,7 +105,7 @@ CODE_SAMPLE
|
||||
}
|
||||
|
||||
// special case for Laravel Collection macro magic
|
||||
$fetchedLocalPropertyNameToTypes = $this->resolveFetchedLocalPropertyNameToTypes($node);
|
||||
$fetchedLocalPropertyNameToTypes = $this->resolveFetchedLocalPropertyNameToType($node);
|
||||
|
||||
$propertyNames = $this->getClassPropertyNames($node);
|
||||
|
||||
@ -123,49 +125,50 @@ CODE_SAMPLE
|
||||
|
||||
$newProperties = $this->createNewProperties($fetchedLocalPropertyNameToTypes, $propertiesToComplete);
|
||||
|
||||
$node->stmts = array_merge_recursive($newProperties, $node->stmts);
|
||||
$node->stmts = array_merge($newProperties, $node->stmts);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[][][] $fetchedLocalPropertyNameToTypes
|
||||
* @param Type[] $fetchedLocalPropertyNameToTypes
|
||||
* @param string[] $propertiesToComplete
|
||||
* @return Property[]
|
||||
*/
|
||||
private function createNewProperties(array $fetchedLocalPropertyNameToTypes, array $propertiesToComplete): array
|
||||
{
|
||||
$newProperties = [];
|
||||
foreach ($fetchedLocalPropertyNameToTypes as $propertyName => $propertyTypes) {
|
||||
foreach ($fetchedLocalPropertyNameToTypes as $propertyName => $propertyType) {
|
||||
if (! in_array($propertyName, $propertiesToComplete, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$propertyTypes = Arrays::flatten($propertyTypes);
|
||||
$propertyTypesAsString = implode('|', $propertyTypes);
|
||||
|
||||
$propertyBuilder = $this->builderFactory->property($propertyName);
|
||||
$propertyBuilder->makePublic();
|
||||
$property = $propertyBuilder->getNode();
|
||||
|
||||
if ($this->isAtLeastPhpVersion('7.4') && count($propertyTypes) === 1) {
|
||||
$propertyBuilder->setType($propertyTypes[0]);
|
||||
$newProperty = $propertyBuilder->getNode();
|
||||
} else {
|
||||
$newProperty = $propertyBuilder->getNode();
|
||||
if ($propertyTypesAsString) {
|
||||
$this->docBlockManipulator->changeVarTag($newProperty, $propertyTypesAsString);
|
||||
if ($this->isAtLeastPhpVersion('7.4')) {
|
||||
$phpStanNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyType);
|
||||
if ($phpStanNode) {
|
||||
$property->type = $phpStanNode;
|
||||
} else {
|
||||
// fallback to doc type in PHP 7.4
|
||||
$this->docBlockManipulator->changeVarTag($property, $propertyType);
|
||||
}
|
||||
} else {
|
||||
$this->docBlockManipulator->changeVarTag($property, $propertyType);
|
||||
}
|
||||
|
||||
$newProperties[] = $newProperty;
|
||||
$newProperties[] = $property;
|
||||
}
|
||||
|
||||
return $newProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][][]
|
||||
* @return Type[]
|
||||
*/
|
||||
private function resolveFetchedLocalPropertyNameToTypes(Class_ $class): array
|
||||
private function resolveFetchedLocalPropertyNameToType(Class_ $class): array
|
||||
{
|
||||
$fetchedLocalPropertyNameToTypes = [];
|
||||
|
||||
@ -199,7 +202,13 @@ CODE_SAMPLE
|
||||
$fetchedLocalPropertyNameToTypes[$propertyName][] = $propertyFetchType;
|
||||
});
|
||||
|
||||
return $fetchedLocalPropertyNameToTypes;
|
||||
// normalize types to union
|
||||
$fetchedLocalPropertyNameToType = [];
|
||||
foreach ($fetchedLocalPropertyNameToTypes as $name => $types) {
|
||||
$fetchedLocalPropertyNameToType[$name] = $this->typeFactory->createMixedPassedOrUnionType($types);
|
||||
}
|
||||
|
||||
return $fetchedLocalPropertyNameToType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,34 +218,23 @@ CODE_SAMPLE
|
||||
{
|
||||
$propertyNames = [];
|
||||
|
||||
$this->traverseNodesWithCallable($class->stmts, function (Node $node) use (&$propertyNames) {
|
||||
if (! $node instanceof Property) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$propertyNames[] = $this->getName($node);
|
||||
});
|
||||
foreach ($class->getProperties() as $property) {
|
||||
$propertyNames[] = $this->getName($property);
|
||||
}
|
||||
|
||||
return $propertyNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function resolvePropertyFetchType(Node $node): array
|
||||
private function resolvePropertyFetchType(Node $node): Type
|
||||
{
|
||||
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
|
||||
|
||||
// possible get type
|
||||
if ($parentNode instanceof Assign) {
|
||||
$assignedValueStaticType = $this->getStaticType($parentNode->expr);
|
||||
if ($assignedValueStaticType) {
|
||||
return $this->staticTypeMapper->mapPHPStanTypeToStrings($assignedValueStaticType);
|
||||
}
|
||||
return $this->getStaticType($parentNode->expr);
|
||||
}
|
||||
|
||||
// fallback type
|
||||
return ['mixed'];
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
private function shouldSkipForLaravelCollection(Node $node): bool
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\If_\RemoveAlwaysTrueConditionSetInConstructorRector\Fixture;
|
||||
|
||||
final class Strings
|
||||
final class TestingStrings
|
||||
{
|
||||
private $value;
|
||||
private $smallValue;
|
||||
@ -31,7 +31,7 @@ final class Strings
|
||||
|
||||
namespace Rector\CodeQuality\Tests\Rector\If_\RemoveAlwaysTrueConditionSetInConstructorRector\Fixture;
|
||||
|
||||
final class Strings
|
||||
final class TestingStrings
|
||||
{
|
||||
private $value;
|
||||
private $smallValue;
|
||||
|
@ -11,19 +11,20 @@ use Rector\CodingStyle\Naming\ClassNaming;
|
||||
use Rector\Contract\PhpParser\Node\CommanderInterface;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\PHPStan\Type\FullyQualifiedObjectType;
|
||||
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
|
||||
|
||||
final class UseAddingCommander implements CommanderInterface
|
||||
{
|
||||
/**
|
||||
* @var string[][]
|
||||
* @var FullyQualifiedObjectType[][]
|
||||
*/
|
||||
private $useImportsInFilePath = [];
|
||||
private $useImportTypesInFilePath = [];
|
||||
|
||||
/**
|
||||
* @var string[][]
|
||||
* @var FullyQualifiedObjectType[][]
|
||||
*/
|
||||
private $functionUseImportsInFilePath = [];
|
||||
private $functionUseImportTypesInFilePath = [];
|
||||
|
||||
/**
|
||||
* @var UseImportsAdder
|
||||
@ -57,7 +58,7 @@ final class UseAddingCommander implements CommanderInterface
|
||||
$this->betterNodeFinder = $betterNodeFinder;
|
||||
}
|
||||
|
||||
public function addUseImport(Node $node, string $useImport): void
|
||||
public function addUseImport(Node $node, FullyQualifiedObjectType $fullyQualifiedObjectType): void
|
||||
{
|
||||
/** @var SmartFileInfo|null $fileInfo */
|
||||
$fileInfo = $node->getAttribute(AttributeKey::FILE_INFO);
|
||||
@ -66,14 +67,14 @@ final class UseAddingCommander implements CommanderInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$this->useImportsInFilePath[$fileInfo->getRealPath()][] = $useImport;
|
||||
$this->useImportTypesInFilePath[$fileInfo->getRealPath()][] = $fullyQualifiedObjectType;
|
||||
}
|
||||
|
||||
public function addFunctionUseImport(Node $node, string $functionUseImport): void
|
||||
public function addFunctionUseImport(Node $node, FullyQualifiedObjectType $fullyQualifiedObjectType): void
|
||||
{
|
||||
/** @var SmartFileInfo $fileInfo */
|
||||
$fileInfo = $node->getAttribute(AttributeKey::FILE_INFO);
|
||||
$this->functionUseImportsInFilePath[$fileInfo->getRealPath()][] = $functionUseImport;
|
||||
$this->functionUseImportTypesInFilePath[$fileInfo->getRealPath()][] = $fullyQualifiedObjectType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,50 +90,48 @@ final class UseAddingCommander implements CommanderInterface
|
||||
|
||||
$filePath = $this->getRealPathFromNode($nodes[0]);
|
||||
|
||||
$useImports = $this->useImportsInFilePath[$filePath] ?? [];
|
||||
$functionUseImports = $this->functionUseImportsInFilePath[$filePath] ?? [];
|
||||
$useImportTypes = $this->useImportTypesInFilePath[$filePath] ?? [];
|
||||
$functionUseImportTypes = $this->functionUseImportTypesInFilePath[$filePath] ?? [];
|
||||
|
||||
// nothing to import
|
||||
if ($useImports === [] && $functionUseImports === []) {
|
||||
if ($useImportTypes === [] && $functionUseImportTypes === []) {
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
// clear applied imports, so isActive() doesn't return any false positives
|
||||
unset($this->useImportsInFilePath[$filePath], $this->functionUseImportsInFilePath[$filePath]);
|
||||
unset($this->useImportTypesInFilePath[$filePath], $this->functionUseImportTypesInFilePath[$filePath]);
|
||||
|
||||
// A. has namespace? add under it
|
||||
$namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class);
|
||||
if ($namespace instanceof Namespace_) {
|
||||
$this->useImportsAdder->addImportsToNamespace($namespace, $useImports, $functionUseImports);
|
||||
$this->useImportsAdder->addImportsToNamespace($namespace, $useImportTypes, $functionUseImportTypes);
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
// B. no namespace? add in the top
|
||||
return $this->useImportsAdder->addImportsToStmts($nodes, $useImports, $functionUseImports);
|
||||
return $this->useImportsAdder->addImportsToStmts($nodes, $useImportTypes, $functionUseImportTypes);
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return count($this->useImportsInFilePath) > 0 || count($this->functionUseImportsInFilePath) > 0;
|
||||
return count($this->useImportTypesInFilePath) > 0 || count($this->functionUseImportTypesInFilePath) > 0;
|
||||
}
|
||||
|
||||
public function isShortImported(Node $node, string $fullyQualifiedName): bool
|
||||
public function isShortImported(Node $node, FullyQualifiedObjectType $fullyQualifiedObjectType): bool
|
||||
{
|
||||
$filePath = $this->getRealPathFromNode($node);
|
||||
$shortName = $this->classNaming->getShortName($fullyQualifiedName);
|
||||
$shortName = $fullyQualifiedObjectType->getShortName();
|
||||
|
||||
$fileUseImports = $this->useImportsInFilePath[$filePath] ?? [];
|
||||
$fileUseImports = $this->useImportTypesInFilePath[$filePath] ?? [];
|
||||
foreach ($fileUseImports as $fileUseImport) {
|
||||
$shortFileUseImport = $this->classNaming->getShortName($fileUseImport);
|
||||
if ($shortFileUseImport === $shortName) {
|
||||
if ($fileUseImport->getShortName() === $shortName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$fileFunctionUseImports = $this->functionUseImportsInFilePath[$filePath] ?? [];
|
||||
foreach ($fileFunctionUseImports as $fileFunctionUseImport) {
|
||||
$shortFileFunctionUseImport = $this->classNaming->getShortName($fileFunctionUseImport);
|
||||
if ($shortFileFunctionUseImport === $shortName) {
|
||||
$fileFunctionUseImportTypes = $this->functionUseImportTypesInFilePath[$filePath] ?? [];
|
||||
foreach ($fileFunctionUseImportTypes as $fileFunctionUseImportType) {
|
||||
if ($fileFunctionUseImportType->getShortName() === $fullyQualifiedObjectType->getShortName()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -140,21 +139,33 @@ final class UseAddingCommander implements CommanderInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isImportShortable(Node $node, string $fullyQualifiedName): bool
|
||||
public function isImportShortable(Node $node, FullyQualifiedObjectType $fullyQualifiedObjectType): bool
|
||||
{
|
||||
$filePath = $this->getRealPathFromNode($node);
|
||||
|
||||
$fileUseImports = $this->useImportsInFilePath[$filePath] ?? [];
|
||||
if (in_array($fullyQualifiedName, $fileUseImports, true)) {
|
||||
return true;
|
||||
}
|
||||
$fileUseImportTypes = $this->useImportTypesInFilePath[$filePath] ?? [];
|
||||
|
||||
$functionUseImports = $this->functionUseImportsInFilePath[$filePath] ?? [];
|
||||
if (in_array($fullyQualifiedName, $functionUseImports, true)) {
|
||||
return true;
|
||||
foreach ($fileUseImportTypes as $useImportType) {
|
||||
if ($fullyQualifiedObjectType->equals($useImportType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// dump($fileUseImportTypes);
|
||||
// dump($fullyQualifiedObjectType);
|
||||
// die;
|
||||
//
|
||||
// if (in_array($fullyQualifiedObjectType, $fileUseImportTypes, true)) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// $functionUseImports = $this->functionUseImportsInFilePath[$filePath] ?? [];
|
||||
// if (in_array($fullyQualifiedObjectType, $functionUseImports, true)) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
}
|
||||
|
||||
public function analyseFileInfoUseStatements(Node $node): void
|
||||
@ -162,34 +173,35 @@ final class UseAddingCommander implements CommanderInterface
|
||||
$filePath = $this->getRealPathFromNode($node);
|
||||
|
||||
// already analysed
|
||||
if (isset($this->useImportsInFilePath[$filePath])) {
|
||||
if (isset($this->useImportTypesInFilePath[$filePath])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$usedImports = $this->usedImportsResolver->resolveForNode($node);
|
||||
|
||||
foreach ($usedImports as $usedImport) {
|
||||
$this->useImportsInFilePath[$filePath][] = $usedImport;
|
||||
$this->useImportTypesInFilePath[$filePath][] = $usedImport;
|
||||
}
|
||||
}
|
||||
|
||||
public function hasImport(Name $name, string $fullyQualifiedName): bool
|
||||
public function hasImport(Name $name, FullyQualifiedObjectType $fullyQualifiedObjectType): bool
|
||||
{
|
||||
$filePath = $this->getRealPathFromNode($name);
|
||||
|
||||
return in_array($fullyQualifiedName, $this->useImportsInFilePath[$filePath] ?? [], true);
|
||||
return in_array($fullyQualifiedObjectType, $this->useImportTypesInFilePath[$filePath] ?? [], true);
|
||||
}
|
||||
|
||||
public function canImportBeAdded(Name $name, string $import): bool
|
||||
public function canImportBeAdded(Name $name, FullyQualifiedObjectType $fullyQualifiedObjectType): bool
|
||||
{
|
||||
$shortImport = $this->classNaming->getShortName($import);
|
||||
$shortImport = $fullyQualifiedObjectType->getShortName();
|
||||
|
||||
$filePath = $this->getRealPathFromNode($name);
|
||||
|
||||
foreach ($this->useImportsInFilePath[$filePath] ?? [] as $importsInClass) {
|
||||
$shortImportInClass = $this->classNaming->getShortName($importsInClass);
|
||||
if ($importsInClass !== $import && $shortImportInClass === $shortImport) {
|
||||
return true;
|
||||
foreach ($this->useImportTypesInFilePath[$filePath] ?? [] as $importsInClass) {
|
||||
if ($importsInClass !== $fullyQualifiedObjectType) {
|
||||
if ($importsInClass->getShortName() === $shortImport) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,11 @@
|
||||
namespace Rector\CodingStyle\Application;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PhpParser\Node\Stmt\UseUse;
|
||||
use Rector\CodingStyle\Imports\UsedImportsResolver;
|
||||
use Rector\PHPStan\Type\FullyQualifiedObjectType;
|
||||
|
||||
final class UseImportsAdder
|
||||
{
|
||||
@ -24,44 +23,48 @@ final class UseImportsAdder
|
||||
|
||||
/**
|
||||
* @param Stmt[] $stmts
|
||||
* @param string[] $useImports
|
||||
* @param string[] $functionUseImports
|
||||
* @param FullyQualifiedObjectType[] $useImportTypes
|
||||
* @param FullyQualifiedObjectType[] $functionUseImportTypes
|
||||
* @return Stmt[]
|
||||
*/
|
||||
public function addImportsToStmts(array $stmts, array $useImports, array $functionUseImports): array
|
||||
public function addImportsToStmts(array $stmts, array $useImportTypes, array $functionUseImportTypes): array
|
||||
{
|
||||
$existingUseImports = $this->usedImportsResolver->resolveForStmts($stmts);
|
||||
$existingUseImportTypes = $this->usedImportsResolver->resolveForStmts($stmts);
|
||||
$existingFunctionUseImports = $this->usedImportsResolver->resolveFunctionImportsForStmts($stmts);
|
||||
|
||||
$useImports = array_unique($useImports);
|
||||
$functionUseImports = array_unique($functionUseImports);
|
||||
$useImportTypes = $this->diffFullyQualifiedObjectTypes($useImportTypes, $existingUseImportTypes);
|
||||
$functionUseImportTypes = $this->diffFullyQualifiedObjectTypes(
|
||||
$functionUseImportTypes,
|
||||
$existingFunctionUseImports
|
||||
);
|
||||
|
||||
$useImports = array_diff($useImports, $existingUseImports);
|
||||
$functionUseImports = array_diff($functionUseImports, $existingFunctionUseImports);
|
||||
|
||||
$newUses = $this->createUses($useImports, $functionUseImports, null);
|
||||
$newUses = $this->createUses($useImportTypes, $functionUseImportTypes, null);
|
||||
|
||||
return array_merge($newUses, $stmts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $useImports
|
||||
* @param string[] $functionUseImports
|
||||
* @param FullyQualifiedObjectType[] $useImportTypes
|
||||
* @param FullyQualifiedObjectType[] $functionUseImportTypes
|
||||
*/
|
||||
public function addImportsToNamespace(Namespace_ $namespace, array $useImports, array $functionUseImports): void
|
||||
{
|
||||
public function addImportsToNamespace(
|
||||
Namespace_ $namespace,
|
||||
array $useImportTypes,
|
||||
array $functionUseImportTypes
|
||||
): void {
|
||||
$namespaceName = $this->getNamespaceName($namespace);
|
||||
|
||||
$existingUseImports = $this->usedImportsResolver->resolveForNode($namespace);
|
||||
$existingFunctionUseImports = $this->usedImportsResolver->resolveFunctionImportsForNode($namespace);
|
||||
$existingUseImportTypes = $this->usedImportsResolver->resolveForNode($namespace);
|
||||
$existingFunctionUseImportTypes = $this->usedImportsResolver->resolveFunctionImportsForStmts($namespace->stmts);
|
||||
|
||||
$useImports = array_unique($useImports);
|
||||
$functionUseImports = array_unique($functionUseImports);
|
||||
$useImportTypes = $this->diffFullyQualifiedObjectTypes($useImportTypes, $existingUseImportTypes);
|
||||
|
||||
$useImports = array_diff($useImports, $existingUseImports);
|
||||
$functionUseImports = array_diff($functionUseImports, $existingFunctionUseImports);
|
||||
$functionUseImportTypes = $this->diffFullyQualifiedObjectTypes(
|
||||
$functionUseImportTypes,
|
||||
$existingFunctionUseImportTypes
|
||||
);
|
||||
|
||||
$newUses = $this->createUses($useImports, $functionUseImports, $namespaceName);
|
||||
$newUses = $this->createUses($useImportTypes, $functionUseImportTypes, $namespaceName);
|
||||
$namespace->stmts = array_merge($newUses, $namespace->stmts);
|
||||
}
|
||||
|
||||
@ -74,13 +77,15 @@ final class UseImportsAdder
|
||||
return $namespace->name->toString();
|
||||
}
|
||||
|
||||
private function isCurrentNamespace(?string $namespaceName, string $useImports): bool
|
||||
{
|
||||
private function isCurrentNamespace(
|
||||
string $namespaceName,
|
||||
FullyQualifiedObjectType $fullyQualifiedObjectType
|
||||
): bool {
|
||||
if ($namespaceName === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$afterCurrentNamespace = Strings::after($useImports, $namespaceName . '\\');
|
||||
$afterCurrentNamespace = Strings::after($fullyQualifiedObjectType->getClassName(), $namespaceName . '\\');
|
||||
if (! $afterCurrentNamespace) {
|
||||
return false;
|
||||
}
|
||||
@ -89,33 +94,53 @@ final class UseImportsAdder
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $useImports
|
||||
* @param string[] $functionUseImports
|
||||
* @param FullyQualifiedObjectType[] $useImportTypes
|
||||
* @param FullyQualifiedObjectType[] $functionUseImportTypes
|
||||
* @return Use_[]
|
||||
*/
|
||||
private function createUses(array $useImports, array $functionUseImports, ?string $namespaceName): array
|
||||
private function createUses(array $useImportTypes, array $functionUseImportTypes, ?string $namespaceName): array
|
||||
{
|
||||
if ($namespaceName === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$newUses = [];
|
||||
|
||||
foreach ($useImports as $useImport) {
|
||||
if ($this->isCurrentNamespace($namespaceName, $useImport)) {
|
||||
foreach ($useImportTypes as $useImportType) {
|
||||
if ($this->isCurrentNamespace($namespaceName, $useImportType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// already imported in previous cycle
|
||||
$useUse = new UseUse(new Name($useImport));
|
||||
$newUses[] = new Use_([$useUse]);
|
||||
$newUses[] = $useImportType->getUseNode();
|
||||
}
|
||||
|
||||
foreach ($functionUseImports as $functionUseImport) {
|
||||
if ($this->isCurrentNamespace($namespaceName, $functionUseImport)) {
|
||||
foreach ($functionUseImportTypes as $functionUseImportType) {
|
||||
if ($this->isCurrentNamespace($namespaceName, $functionUseImportType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// already imported in previous cycle
|
||||
$useUse = new UseUse(new Name($functionUseImport), null, Use_::TYPE_FUNCTION);
|
||||
$newUses[] = new Use_([$useUse]);
|
||||
$newUses[] = $functionUseImportType->getFunctionUseNode();
|
||||
}
|
||||
|
||||
return $newUses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FullyQualifiedObjectType[] $mainTypes
|
||||
* @param FullyQualifiedObjectType[] $typesToRemove
|
||||
* @return FullyQualifiedObjectType[]
|
||||
*/
|
||||
private function diffFullyQualifiedObjectTypes(array $mainTypes, array $typesToRemove): array
|
||||
{
|
||||
foreach ($mainTypes as $key => $mainType) {
|
||||
foreach ($typesToRemove as $typeToRemove) {
|
||||
if ($mainType->equals($typeToRemove)) {
|
||||
unset($mainTypes[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($mainTypes);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
|
||||
final class ShortNameResolver
|
||||
@ -21,9 +22,15 @@ final class ShortNameResolver
|
||||
*/
|
||||
private $shortNamesByNamespaceObjectHash = [];
|
||||
|
||||
public function __construct(CallableNodeTraverser $callableNodeTraverser)
|
||||
/**
|
||||
* @var NameResolver
|
||||
*/
|
||||
private $nameResolver;
|
||||
|
||||
public function __construct(CallableNodeTraverser $callableNodeTraverser, NameResolver $nameResolver)
|
||||
{
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
$this->nameResolver = $nameResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,23 +40,20 @@ final class ShortNameResolver
|
||||
{
|
||||
/** @var Namespace_|null $namespace */
|
||||
$namespace = $node->getAttribute(AttributeKey::NAMESPACE_NODE);
|
||||
|
||||
if ($namespace === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$namespaceHash = spl_object_hash($namespace);
|
||||
if (isset($this->shortNamesByNamespaceObjectHash[$namespaceHash])) {
|
||||
return $this->shortNamesByNamespaceObjectHash[$namespaceHash];
|
||||
$namespaceName = $this->nameResolver->getName($namespace);
|
||||
|
||||
if (isset($this->shortNamesByNamespaceObjectHash[$namespaceName])) {
|
||||
return $this->shortNamesByNamespaceObjectHash[$namespaceName];
|
||||
}
|
||||
|
||||
if ($namespace instanceof Namespace_) {
|
||||
$shortNames = $this->resolveForNamespace($namespace);
|
||||
$this->shortNamesByNamespaceObjectHash[$namespaceHash] = $shortNames;
|
||||
return $shortNames;
|
||||
}
|
||||
$shortNames = $this->resolveForNamespace($namespace);
|
||||
$this->shortNamesByNamespaceObjectHash[$namespaceName] = $shortNames;
|
||||
|
||||
return [];
|
||||
return $shortNames;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@ use PhpParser\Node\Stmt\UseUse;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
use Rector\PHPStan\Type\FullyQualifiedObjectType;
|
||||
|
||||
final class UsedImportsResolver
|
||||
{
|
||||
@ -40,7 +41,7 @@ final class UsedImportsResolver
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @return FullyQualifiedObjectType[]
|
||||
*/
|
||||
public function resolveForNode(Node $node): array
|
||||
{
|
||||
@ -54,7 +55,7 @@ final class UsedImportsResolver
|
||||
|
||||
/**
|
||||
* @param Stmt[] $stmts
|
||||
* @return string[]
|
||||
* @return FullyQualifiedObjectType[]
|
||||
*/
|
||||
public function resolveForStmts(array $stmts): array
|
||||
{
|
||||
@ -67,7 +68,7 @@ final class UsedImportsResolver
|
||||
if ($class !== null) {
|
||||
$className = $this->nameResolver->getName($class);
|
||||
if ($className !== null) {
|
||||
$usedImports[] = $className;
|
||||
$usedImports[] = new FullyQualifiedObjectType($className);
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +76,7 @@ final class UsedImportsResolver
|
||||
UseUse $useUse,
|
||||
string $name
|
||||
) use (&$usedImports): void {
|
||||
$usedImports[] = $name;
|
||||
$usedImports[] = new FullyQualifiedObjectType($name);
|
||||
});
|
||||
|
||||
return $usedImports;
|
||||
@ -83,7 +84,7 @@ final class UsedImportsResolver
|
||||
|
||||
/**
|
||||
* @param Stmt[] $stmts
|
||||
* @return string[]
|
||||
* @return FullyQualifiedObjectType[]
|
||||
*/
|
||||
public function resolveFunctionImportsForStmts(array $stmts): array
|
||||
{
|
||||
@ -93,22 +94,14 @@ final class UsedImportsResolver
|
||||
UseUse $useUse,
|
||||
string $name
|
||||
) use (&$usedFunctionImports): void {
|
||||
$usedFunctionImports[] = $name;
|
||||
$usedFunctionImports[] = new FullyQualifiedObjectType($name);
|
||||
}, Use_::TYPE_FUNCTION);
|
||||
|
||||
return $usedFunctionImports;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveFunctionImportsForNode(Namespace_ $namespace): array
|
||||
{
|
||||
return $this->resolveFunctionImportsForStmts($namespace->stmts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @return FullyQualifiedObjectType[]
|
||||
*/
|
||||
private function resolveForNamespace(Namespace_ $node): array
|
||||
{
|
||||
|
@ -6,7 +6,6 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use PHPStan\Type\MixedType;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
@ -21,15 +20,9 @@ final class VarConstantCommentRector extends AbstractRector
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
public function __construct(DocBlockManipulator $docBlockManipulator, StaticTypeMapper $staticTypeMapper)
|
||||
public function __construct(DocBlockManipulator $docBlockManipulator)
|
||||
{
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
@ -78,21 +71,7 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$staticTypesInStrings = $this->staticTypeMapper->mapPHPStanTypeToStrings($constStaticType);
|
||||
|
||||
// nothing we can do
|
||||
if ($staticTypesInStrings === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$varTypeInfo = $this->docBlockManipulator->getVarTypeInfo($node);
|
||||
|
||||
if ($varTypeInfo && $varTypeInfo->getTypes() === $staticTypesInStrings) {
|
||||
// already set
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->docBlockManipulator->changeVarTag($node, implode('|', $staticTypesInStrings));
|
||||
$this->docBlockManipulator->changeVarTag($node, $constStaticType);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PhpParser\Node\Stmt\PropertyProperty;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\IterableType;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
@ -139,17 +141,8 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$varTypeInfo = $this->docBlockManipulator->getVarTypeInfo($property);
|
||||
if ($varTypeInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $varTypeInfo->isIterable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// skip nullable
|
||||
if ($varTypeInfo->isNullable()) {
|
||||
$varType = $this->docBlockManipulator->getVarType($property);
|
||||
if (! $varType instanceof ArrayType && ! $varType instanceof IterableType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ namespace Rector\CodingStyle\Rector\Namespace_;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
@ -12,16 +11,15 @@ use PhpParser\Node\Stmt\UseUse;
|
||||
use Rector\CodingStyle\Application\UseAddingCommander;
|
||||
use Rector\CodingStyle\Imports\AliasUsesResolver;
|
||||
use Rector\CodingStyle\Imports\ShortNameResolver;
|
||||
use Rector\CodingStyle\Naming\ClassNaming;
|
||||
use Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\ImportFullyQualifiedNamesRectorTest;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\PHPStan\Type\FullyQualifiedObjectType;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @see ImportFullyQualifiedNamesRectorTest
|
||||
* @see \Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\ImportFullyQualifiedNamesRectorTest
|
||||
*/
|
||||
final class ImportFullyQualifiedNamesRector extends AbstractRector
|
||||
{
|
||||
@ -40,11 +38,6 @@ final class ImportFullyQualifiedNamesRector extends AbstractRector
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @var ClassNaming
|
||||
*/
|
||||
private $classNaming;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
@ -67,14 +60,12 @@ final class ImportFullyQualifiedNamesRector extends AbstractRector
|
||||
|
||||
public function __construct(
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
ClassNaming $classNaming,
|
||||
AliasUsesResolver $aliasUsesResolver,
|
||||
UseAddingCommander $useAddingCommander,
|
||||
ShortNameResolver $shortNameResolver,
|
||||
bool $shouldImportDocBlocks = true
|
||||
) {
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->classNaming = $classNaming;
|
||||
$this->shouldImportDocBlocks = $shouldImportDocBlocks;
|
||||
$this->useAddingCommander = $useAddingCommander;
|
||||
$this->aliasUsesResolver = $aliasUsesResolver;
|
||||
@ -126,6 +117,11 @@ CODE_SAMPLE
|
||||
$this->useAddingCommander->analyseFileInfoUseStatements($node);
|
||||
|
||||
if ($node instanceof Name) {
|
||||
$staticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($node);
|
||||
if (! $staticType instanceof FullyQualifiedObjectType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->canBeNameImported($node)) {
|
||||
return null;
|
||||
}
|
||||
@ -154,77 +150,88 @@ CODE_SAMPLE
|
||||
private function importNamesAndCollectNewUseStatements(Name $name): ?Name
|
||||
{
|
||||
$originalName = $name->getAttribute('originalName');
|
||||
|
||||
if (! $originalName instanceof Name) {
|
||||
// not sure what to do
|
||||
return null;
|
||||
}
|
||||
|
||||
$staticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($name);
|
||||
if (! $staticType instanceof FullyQualifiedObjectType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// the short name is already used, skip it
|
||||
// @todo this is duplicated check of - $this->useAddingCommander->isShortImported?
|
||||
$shortName = $this->classNaming->getShortName($name->toString());
|
||||
$shortName = $staticType->getShortName();
|
||||
|
||||
if ($this->isShortNameAlreadyUsedForDifferentFqn($name, $shortName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fullyQualifiedName = $this->getName($name);
|
||||
// $fullyQualifiedName = $this->getName($name);
|
||||
|
||||
// the similar end is already imported → skip
|
||||
if ($this->shouldSkipName($name, $fullyQualifiedName)) {
|
||||
// $fullyQualifiedObjectType = $this->staticTypeMapper->mapStringToPHPStanType($fullyQualifiedName);
|
||||
if (! $staticType instanceof FullyQualifiedObjectType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$shortName = $this->classNaming->getShortName($fullyQualifiedName);
|
||||
// the similar end is already imported → skip
|
||||
if ($this->shouldSkipName($name, $staticType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->useAddingCommander->isShortImported($name, $fullyQualifiedName)) {
|
||||
if ($this->useAddingCommander->isImportShortable($name, $fullyQualifiedName)) {
|
||||
$shortName = $staticType->getShortName();
|
||||
|
||||
if ($this->useAddingCommander->isShortImported($name, $staticType)) {
|
||||
if ($this->useAddingCommander->isImportShortable($name, $staticType)) {
|
||||
return new Name($shortName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->useAddingCommander->hasImport($name, $fullyQualifiedName)) {
|
||||
if (! $this->useAddingCommander->hasImport($name, $staticType)) {
|
||||
$parentNode = $name->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if ($parentNode instanceof FuncCall) {
|
||||
$this->useAddingCommander->addFunctionUseImport($name, $fullyQualifiedName);
|
||||
$this->useAddingCommander->addFunctionUseImport($name, $staticType);
|
||||
} else {
|
||||
$this->useAddingCommander->addUseImport($name, $fullyQualifiedName);
|
||||
$this->useAddingCommander->addUseImport($name, $staticType);
|
||||
}
|
||||
}
|
||||
|
||||
// possibly aliased
|
||||
if (in_array($fullyQualifiedName, $this->aliasedUses, true)) {
|
||||
return null;
|
||||
foreach ($this->aliasedUses as $aliasUse) {
|
||||
if ($staticType->getClassName() === $aliasUse) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new Name($shortName);
|
||||
}
|
||||
|
||||
// 1. name is fully qualified → import it
|
||||
private function shouldSkipName(Name $name, string $fullyQualifiedName): bool
|
||||
private function shouldSkipName(Name $name, FullyQualifiedObjectType $fullyQualifiedObjectType): bool
|
||||
{
|
||||
$shortName = $this->classNaming->getShortName($fullyQualifiedName);
|
||||
$shortName = $fullyQualifiedObjectType->getShortName();
|
||||
|
||||
$parentNode = $name->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if ($parentNode instanceof ConstFetch) { // is true, false, null etc.
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->isNames($name, ['self', 'parent', 'static'])) {
|
||||
return true;
|
||||
}
|
||||
// if ($parentNode instanceof ConstFetch) { // is true, false, null etc.
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// skip native function calls
|
||||
if ($parentNode instanceof FuncCall && ! Strings::contains($fullyQualifiedName, '\\')) {
|
||||
return true;
|
||||
}
|
||||
// if ($parentNode instanceof FuncCall) {
|
||||
// dump($fullyQualifiedObjectType);
|
||||
// die;
|
||||
//// } && ! Strings::contains($fullyQualifiedObjectType, '\\')) {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
// nothing to change
|
||||
if ($shortName === $fullyQualifiedName) {
|
||||
return false;
|
||||
}
|
||||
// if ($fullyQualifiedObjectType->getShortNameType() === $fullyQualifiedObjectType) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
foreach ($this->aliasedUses as $aliasedUse) {
|
||||
// its aliased, we cannot just rename it
|
||||
@ -233,7 +240,7 @@ CODE_SAMPLE
|
||||
}
|
||||
}
|
||||
|
||||
return $this->useAddingCommander->canImportBeAdded($name, $fullyQualifiedName);
|
||||
return $this->useAddingCommander->canImportBeAdded($name, $fullyQualifiedObjectType);
|
||||
}
|
||||
|
||||
// is already used
|
||||
|
@ -2,16 +2,17 @@
|
||||
|
||||
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;
|
||||
|
||||
use Some\Trait_;
|
||||
use Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Another;
|
||||
use Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Some\Trait_;
|
||||
|
||||
final class SameEnd
|
||||
{
|
||||
/**
|
||||
* @param \Some\Trait_ $firstTrait
|
||||
* @param \Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Some\Trait_ $firstTrait
|
||||
* @param Another\Trait_ $secondTrait
|
||||
* @param \Some\Trait_ $thirdTrait
|
||||
* @param \Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Some\Trait_ $thirdTrait
|
||||
*/
|
||||
public function __construct(\Some\Trait_ $firstTrait, Another\Trait_ $secondTrait, \Some\Trait_ $thirdTrait)
|
||||
public function __construct(\Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Some\Trait_ $firstTrait, Another\Trait_ $secondTrait, \Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Some\Trait_ $thirdTrait)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -22,7 +23,8 @@ final class SameEnd
|
||||
|
||||
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Fixture;
|
||||
|
||||
use Some\Trait_;
|
||||
use Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Another;
|
||||
use Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Some\Trait_;
|
||||
|
||||
final class SameEnd
|
||||
{
|
||||
|
@ -10,7 +10,6 @@ final class ImportFullyQualifiedNamesRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideNamespacedClasses()
|
||||
* @dataProvider provideFunctions()
|
||||
*/
|
||||
public function test(string $file): void
|
||||
{
|
||||
@ -20,42 +19,42 @@ final class ImportFullyQualifiedNamesRectorTest extends AbstractRectorTestCase
|
||||
public function provideNamespacedClasses(): Iterator
|
||||
{
|
||||
// same short class with namespace
|
||||
yield [__DIR__ . '/Fixture/same_namespaced_class.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/skip_same_namespaced_used_class.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/include_used_local_class.php.inc'];
|
||||
|
||||
yield [__DIR__ . '/Fixture/fixture.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/double_import.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/double_import_with_existing.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/already_with_use.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/already_class_name.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/no_class.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/short.php.inc'];
|
||||
|
||||
// keep
|
||||
yield [__DIR__ . '/Fixture/keep.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_aliased.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/same_namespaced_class.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/skip_same_namespaced_used_class.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/include_used_local_class.php.inc'];
|
||||
//
|
||||
// yield [__DIR__ . '/Fixture/fixture.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/double_import.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/double_import_with_existing.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/already_with_use.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/already_class_name.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/no_class.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/short.php.inc'];
|
||||
//
|
||||
// // keep
|
||||
// yield [__DIR__ . '/Fixture/keep.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/keep_aliased.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_same_end.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_trait_use.php.inc'];
|
||||
|
||||
// php doc
|
||||
yield [__DIR__ . '/Fixture/import_param_doc.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/doc_combined.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/conflicting_endings.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/already_class_name_in_param_doc.php.inc'];
|
||||
|
||||
// buggy
|
||||
yield [__DIR__ . '/Fixture/many_imports.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_static_method.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_various_request.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/instance_of.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/keep_trait_use.php.inc'];
|
||||
//
|
||||
// // php doc
|
||||
// yield [__DIR__ . '/Fixture/import_param_doc.php.inc'];
|
||||
//// yield [__DIR__ . '/Fixture/doc_combined.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/conflicting_endings.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/already_class_name_in_param_doc.php.inc'];
|
||||
//
|
||||
// // buggy
|
||||
// yield [__DIR__ . '/Fixture/many_imports.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/keep_static_method.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/keep_various_request.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/instance_of.php.inc'];
|
||||
}
|
||||
|
||||
public function provideFunctions(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Fixture/import_function.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/import_function_no_class.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/import_return_doc.php.inc'];
|
||||
// yield [__DIR__ . '/Fixture/import_return_doc.php.inc'];
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Another;
|
||||
|
||||
final class Trait_
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\CodingStyle\Tests\Rector\Namespace_\ImportFullyQualifiedNamesRector\Source\Some;
|
||||
|
||||
final class Trait_
|
||||
{
|
||||
|
||||
}
|
@ -6,6 +6,7 @@ use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\InheritanceType;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
|
||||
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\InversedByNodeInterface;
|
||||
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\MappedByNodeInterface;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
@ -37,7 +38,7 @@ final class DoctrineEntityManipulator
|
||||
|
||||
$phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property);
|
||||
|
||||
$relationTagValueNode = $phpDocInfo->getDoctrineRelationTagValueNode();
|
||||
$relationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class);
|
||||
if ($relationTagValueNode === null) {
|
||||
return null;
|
||||
}
|
||||
@ -84,7 +85,7 @@ final class DoctrineEntityManipulator
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property);
|
||||
$relationTagValueNode = $phpDocInfo->getDoctrineRelationTagValueNode();
|
||||
$relationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class);
|
||||
|
||||
$shouldUpdate = false;
|
||||
if ($relationTagValueNode instanceof MappedByNodeInterface) {
|
||||
@ -121,7 +122,7 @@ final class DoctrineEntityManipulator
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property);
|
||||
if ($phpDocInfo->getDoctrineRelationTagValueNode() === null) {
|
||||
if ($phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class) === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Class_\EntityTagValueNode;
|
||||
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\ColumnTagValueNode;
|
||||
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\IdTagValueNode;
|
||||
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\TableTagValueNode;
|
||||
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
|
||||
@ -81,17 +80,7 @@ final class DoctrineDocBlockResolver
|
||||
return null;
|
||||
}
|
||||
|
||||
return $propertyPhpDocInfo->getDoctrineRelationTagValueNode();
|
||||
}
|
||||
|
||||
public function getDoctrineTableTagValueNode(Class_ $class): ?TableTagValueNode
|
||||
{
|
||||
$classPhpDocInfo = $this->getPhpDocInfo($class);
|
||||
if ($classPhpDocInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $classPhpDocInfo->getByType(TableTagValueNode::class);
|
||||
return $propertyPhpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class);
|
||||
}
|
||||
|
||||
public function isDoctrineProperty(Property $property): bool
|
||||
@ -105,7 +94,7 @@ final class DoctrineDocBlockResolver
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) $propertyPhpDocInfo->getDoctrineRelationTagValueNode();
|
||||
return (bool) $propertyPhpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class);
|
||||
}
|
||||
|
||||
private function getPhpDocInfo(Node $node): ?PhpDocInfo
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace Rector\DomainDrivenDesign\Rector\ObjectToScalar;
|
||||
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\NamespaceAnalyzer;
|
||||
use Rector\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\Rector\AbstractRector;
|
||||
|
||||
@ -24,11 +23,6 @@ abstract class AbstractObjectToScalarRector extends AbstractRector
|
||||
*/
|
||||
protected $betterNodeFinder;
|
||||
|
||||
/**
|
||||
* @var NamespaceAnalyzer
|
||||
*/
|
||||
protected $namespaceAnalyzer;
|
||||
|
||||
/**
|
||||
* @param string[] $valueObjectsToSimpleTypes
|
||||
*/
|
||||
@ -42,11 +36,9 @@ abstract class AbstractObjectToScalarRector extends AbstractRector
|
||||
*/
|
||||
public function autowireAbstractObjectToScalarRectorDependencies(
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
BetterNodeFinder $betterNodeFinder,
|
||||
NamespaceAnalyzer $namespaceAnalyzer
|
||||
BetterNodeFinder $betterNodeFinder
|
||||
): void {
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->betterNodeFinder = $betterNodeFinder;
|
||||
$this->namespaceAnalyzer = $namespaceAnalyzer;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\RectorDefinition\ConfiguredCodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
@ -80,12 +82,15 @@ CODE_SAMPLE
|
||||
|
||||
private function refactorProperty(Property $property): ?Property
|
||||
{
|
||||
foreach ($this->valueObjectsToSimpleTypes as $valueObject => $simpleType) {
|
||||
if (! $this->isObjectType($property, $valueObject)) {
|
||||
foreach ($this->valueObjectsToSimpleTypes as $oldType => $newSimpleType) {
|
||||
$oldObjectType = new ObjectType($oldType);
|
||||
if (! $this->isObjectType($property, $oldObjectType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->docBlockManipulator->changeTypeIncludingChildren($property, $valueObject, $simpleType);
|
||||
$newSimpleType = $this->staticTypeMapper->mapStringToPHPStanType($newSimpleType);
|
||||
|
||||
$this->docBlockManipulator->changeType($property, $oldObjectType, $newSimpleType);
|
||||
|
||||
return $property;
|
||||
}
|
||||
@ -95,21 +100,20 @@ CODE_SAMPLE
|
||||
|
||||
private function refactorNullableType(NullableType $nullableType): ?NullableType
|
||||
{
|
||||
foreach ($this->valueObjectsToSimpleTypes as $valueObject => $simpleType) {
|
||||
$typeName = $this->getName($nullableType->type);
|
||||
if ($typeName === null) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->valueObjectsToSimpleTypes as $valueObject => $newSimpleType) {
|
||||
$newSimpleType = $this->staticTypeMapper->mapStringToPHPStanType($newSimpleType);
|
||||
|
||||
/** @var string $valueObject */
|
||||
if (! is_a($typeName, $valueObject, true)) {
|
||||
$nullableStaticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($nullableType->type);
|
||||
$valueObjectType = new ObjectType($valueObject);
|
||||
|
||||
if (! $valueObjectType->equals($nullableStaticType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// in method parameter update docs as well
|
||||
$parentNode = $nullableType->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if ($parentNode instanceof Param) {
|
||||
$this->processParamNode($nullableType, $parentNode, $simpleType);
|
||||
$this->processParamNode($nullableType, $parentNode, $newSimpleType);
|
||||
}
|
||||
|
||||
return $nullableType;
|
||||
@ -120,8 +124,8 @@ CODE_SAMPLE
|
||||
|
||||
private function refactorVariableNode(Variable $variable): ?Variable
|
||||
{
|
||||
foreach ($this->valueObjectsToSimpleTypes as $valueObject => $simpleType) {
|
||||
if (! $this->isObjectType($variable, $valueObject)) {
|
||||
foreach ($this->valueObjectsToSimpleTypes as $oldObject => $simpleType) {
|
||||
if (! $this->isObjectType($variable, $oldObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -135,7 +139,10 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->docBlockManipulator->changeTypeIncludingChildren($node, $valueObject, $simpleType);
|
||||
$oldObjectType = new ObjectType($oldObject);
|
||||
$newSimpleType = $this->staticTypeMapper->mapStringToPHPStanType($simpleType);
|
||||
|
||||
$this->docBlockManipulator->changeType($node, $oldObjectType, $newSimpleType);
|
||||
|
||||
return $variable;
|
||||
}
|
||||
@ -143,15 +150,18 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
private function processParamNode(NullableType $nullableType, Param $param, string $newType): void
|
||||
private function processParamNode(NullableType $nullableType, Param $param, Type $newType): void
|
||||
{
|
||||
$classMethodNode = $param->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if ($classMethodNode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oldType = $this->namespaceAnalyzer->resolveTypeToFullyQualified((string) $nullableType->type, $nullableType);
|
||||
$paramStaticType = $this->getStaticType($param);
|
||||
if (! $paramStaticType instanceof ObjectType) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->docBlockManipulator->changeTypeIncludingChildren($classMethodNode, $oldType, $newType);
|
||||
$this->docBlockManipulator->changeType($classMethodNode, $paramStaticType, $newType);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use Rector\NetteToSymfony\Annotation\SymfonyRoutePhpDocTagNode;
|
||||
use Rector\NetteToSymfony\Route\RouteInfo;
|
||||
use Rector\NetteToSymfony\Route\RouteInfoFactory;
|
||||
@ -24,6 +25,7 @@ use ReflectionMethod;
|
||||
/**
|
||||
* @see https://doc.nette.org/en/2.4/routing
|
||||
* @see https://symfony.com/doc/current/routing.html
|
||||
*
|
||||
* @see \Rector\NetteToSymfony\Tests\Rector\ClassMethod\RouterListToControllerAnnotationsRetor\RouterListToControllerAnnotationsRectorTest
|
||||
*/
|
||||
final class RouterListToControllerAnnotationsRector extends AbstractRector
|
||||
@ -154,8 +156,11 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$inferedReturnTypes = $this->returnTypeInferer->inferFunctionLike($node);
|
||||
if (! in_array($this->routeListClass, $inferedReturnTypes, true)) {
|
||||
$inferedReturnType = $this->returnTypeInferer->inferFunctionLike($node);
|
||||
|
||||
$routeListObjectType = new ObjectType($this->routeListClass);
|
||||
|
||||
if (! $inferedReturnType->isSuperTypeOf($routeListObjectType)->yes()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,103 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\Php\VarTypeInfo;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
use Rector\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
|
||||
final class ComplexNodeTypeResolver
|
||||
{
|
||||
/**
|
||||
* @var NameResolver
|
||||
*/
|
||||
private $nameResolver;
|
||||
|
||||
/**
|
||||
* @var BetterNodeFinder
|
||||
*/
|
||||
private $betterNodeFinder;
|
||||
|
||||
/**
|
||||
* @var NodeTypeResolver
|
||||
*/
|
||||
private $nodeTypeResolver;
|
||||
|
||||
/**
|
||||
* @var TypeAnalyzer
|
||||
*/
|
||||
private $typeAnalyzer;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
public function __construct(
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
NameResolver $nameResolver,
|
||||
BetterNodeFinder $betterNodeFinder,
|
||||
NodeTypeResolver $nodeTypeResolver,
|
||||
TypeAnalyzer $typeAnalyzer
|
||||
) {
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->nameResolver = $nameResolver;
|
||||
$this->betterNodeFinder = $betterNodeFinder;
|
||||
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on static analysis of code, looking for property assigns
|
||||
*/
|
||||
public function resolvePropertyTypeInfo(Property $property): ?VarTypeInfo
|
||||
{
|
||||
$types = [];
|
||||
|
||||
$propertyDefault = $property->props[0]->default;
|
||||
if ($propertyDefault !== null) {
|
||||
$types[] = $this->staticTypeMapper->mapPhpParserNodeToString($propertyDefault);
|
||||
}
|
||||
|
||||
$classNode = $property->getAttribute(AttributeKey::CLASS_NODE);
|
||||
if (! $classNode instanceof Class_) {
|
||||
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
|
||||
}
|
||||
|
||||
$propertyName = $this->nameResolver->getName($property);
|
||||
if ($propertyName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Assign[] $propertyAssignNodes */
|
||||
$propertyAssignNodes = $this->betterNodeFinder->find([$classNode], function (Node $node) use (
|
||||
$propertyName
|
||||
): bool {
|
||||
if ($node instanceof Assign && $node->var instanceof PropertyFetch) {
|
||||
// is property match
|
||||
return $this->nameResolver->isName($node->var, $propertyName);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
foreach ($propertyAssignNodes as $propertyAssignNode) {
|
||||
$types = array_merge(
|
||||
$types,
|
||||
$this->nodeTypeResolver->resolveSingleTypeToStrings($propertyAssignNode->expr)
|
||||
);
|
||||
}
|
||||
|
||||
$types = array_filter($types);
|
||||
|
||||
return new VarTypeInfo($types, $this->typeAnalyzer, $types);
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ use Rector\NodeTypeResolver\Reflection\ClassReflectionTypesResolver;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
use Rector\PhpParser\Printer\BetterStandardPrinter;
|
||||
use Rector\TypeDeclaration\PHPStan\Type\ObjectTypeSpecifier;
|
||||
|
||||
final class NodeTypeResolver
|
||||
{
|
||||
@ -68,11 +69,6 @@ final class NodeTypeResolver
|
||||
*/
|
||||
private $betterStandardPrinter;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var CallableNodeTraverser
|
||||
*/
|
||||
@ -93,30 +89,36 @@ final class NodeTypeResolver
|
||||
*/
|
||||
private $typeFactory;
|
||||
|
||||
/**
|
||||
* @var ObjectTypeSpecifier
|
||||
*/
|
||||
private $objectTypeSpecifier;
|
||||
|
||||
/**
|
||||
* @param PerNodeTypeResolverInterface[] $perNodeTypeResolvers
|
||||
*/
|
||||
public function __construct(
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
BetterStandardPrinter $betterStandardPrinter,
|
||||
NameResolver $nameResolver,
|
||||
CallableNodeTraverser $callableNodeTraverser,
|
||||
ClassReflectionTypesResolver $classReflectionTypesResolver,
|
||||
Broker $broker,
|
||||
TypeFactory $typeFactory,
|
||||
ObjectTypeSpecifier $objectTypeSpecifier,
|
||||
array $perNodeTypeResolvers
|
||||
) {
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
$this->nameResolver = $nameResolver;
|
||||
|
||||
foreach ($perNodeTypeResolvers as $perNodeTypeResolver) {
|
||||
$this->addPerNodeTypeResolver($perNodeTypeResolver);
|
||||
}
|
||||
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
$this->classReflectionTypesResolver = $classReflectionTypesResolver;
|
||||
$this->broker = $broker;
|
||||
$this->typeFactory = $typeFactory;
|
||||
$this->objectTypeSpecifier = $objectTypeSpecifier;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -295,41 +297,32 @@ final class NodeTypeResolver
|
||||
}
|
||||
}
|
||||
|
||||
return $nodeScope->getType($node);
|
||||
// make object type specific to alias or FQN
|
||||
$staticType = $nodeScope->getType($node);
|
||||
|
||||
if (! $staticType instanceof ObjectType) {
|
||||
return $staticType;
|
||||
}
|
||||
|
||||
return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAlaisedObjectType($node, $staticType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveSingleTypeToStrings(Node $node): array
|
||||
public function resolveNodeToPHPStanType(Expr $expr): Type
|
||||
{
|
||||
if ($this->isArrayType($node)) {
|
||||
$arrayType = $this->getStaticType($node);
|
||||
if ($this->isArrayType($expr)) {
|
||||
$arrayType = $this->getStaticType($expr);
|
||||
if ($arrayType instanceof ArrayType) {
|
||||
$itemTypes = $this->staticTypeMapper->mapPHPStanTypeToStrings($arrayType->getItemType());
|
||||
|
||||
foreach ($itemTypes as $key => $itemType) {
|
||||
$itemTypes[$key] = $itemType . '[]';
|
||||
}
|
||||
|
||||
if (count($itemTypes) > 0) {
|
||||
return [implode('|', $itemTypes)];
|
||||
}
|
||||
return $arrayType;
|
||||
}
|
||||
|
||||
return ['array'];
|
||||
return new ArrayType(new MixedType(), new MixedType());
|
||||
}
|
||||
|
||||
if ($this->isStringOrUnionStringOnlyType($node)) {
|
||||
return ['string'];
|
||||
if ($this->isStringOrUnionStringOnlyType($expr)) {
|
||||
return new StringType();
|
||||
}
|
||||
|
||||
$nodeStaticType = $this->getStaticType($node);
|
||||
if ($nodeStaticType instanceof MixedType) {
|
||||
return ['mixed'];
|
||||
}
|
||||
|
||||
return $this->staticTypeMapper->mapPHPStanTypeToStrings($nodeStaticType);
|
||||
return $this->getStaticType($expr);
|
||||
}
|
||||
|
||||
public function isNullableObjectType(Node $node): bool
|
||||
|
@ -2,7 +2,16 @@
|
||||
|
||||
namespace Rector\NodeTypeResolver\PHPStan\Type;
|
||||
|
||||
use PHPStan\Type\BooleanType;
|
||||
use PHPStan\Type\Constant\ConstantBooleanType;
|
||||
use PHPStan\Type\Constant\ConstantFloatType;
|
||||
use PHPStan\Type\Constant\ConstantIntegerType;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\FloatType;
|
||||
use PHPStan\Type\IntegerType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\UnionType;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
@ -10,6 +19,25 @@ use Rector\PHPStan\TypeFactoryStaticHelper;
|
||||
|
||||
final class TypeFactory
|
||||
{
|
||||
/**
|
||||
* @param Type[] $types
|
||||
*/
|
||||
public function createMixedPassedOrUnionType(array $types): Type
|
||||
{
|
||||
$types = $this->unwrapUnionedTypes($types);
|
||||
$types = $this->uniquateTypes($types);
|
||||
|
||||
if (count($types) === 0) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
if (count($types) === 1) {
|
||||
return $types[0];
|
||||
}
|
||||
|
||||
return TypeFactoryStaticHelper::createUnionObjectType($types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $allTypes
|
||||
* @return ObjectType|UnionType
|
||||
@ -27,4 +55,69 @@ final class TypeFactory
|
||||
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $types
|
||||
* @return Type[]
|
||||
*/
|
||||
private function unwrapUnionedTypes(array $types): array
|
||||
{
|
||||
// unwrap union types
|
||||
$unwrappedTypes = [];
|
||||
foreach ($types as $key => $type) {
|
||||
if ($type instanceof UnionType) {
|
||||
foreach ($type->getTypes() as $subUnionedType) {
|
||||
$unwrappedTypes[] = $subUnionedType;
|
||||
}
|
||||
|
||||
unset($types[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$types = array_merge($types, $unwrappedTypes);
|
||||
|
||||
// re-index
|
||||
return array_values($types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $types
|
||||
* @return Type[]
|
||||
*/
|
||||
private function uniquateTypes(array $types): array
|
||||
{
|
||||
$uniqueTypes = [];
|
||||
foreach ($types as $type) {
|
||||
$type = $this->removeValueFromConstantType($type);
|
||||
|
||||
$typeHash = md5(serialize($type));
|
||||
|
||||
$uniqueTypes[$typeHash] = $type;
|
||||
}
|
||||
|
||||
// re-index
|
||||
return array_values($uniqueTypes);
|
||||
}
|
||||
|
||||
private function removeValueFromConstantType(Type $type): Type
|
||||
{
|
||||
// remove values from constant types
|
||||
if ($type instanceof ConstantFloatType) {
|
||||
return new FloatType();
|
||||
}
|
||||
|
||||
if ($type instanceof ConstantStringType) {
|
||||
return new StringType();
|
||||
}
|
||||
|
||||
if ($type instanceof ConstantIntegerType) {
|
||||
return new IntegerType();
|
||||
}
|
||||
|
||||
if ($type instanceof ConstantBooleanType) {
|
||||
return new BooleanType();
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,9 @@ namespace Rector\NodeTypeResolver\PerNodeTypeResolver;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Param;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\NullType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\UnionType;
|
||||
use Rector\NodeTypeResolver\Contract\PerNodeTypeResolver\PerNodeTypeResolverInterface;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
@ -66,22 +62,9 @@ final class ParamTypeResolver implements PerNodeTypeResolverInterface
|
||||
|
||||
private function resolveTypesFromFunctionDocBlock(Param $param, FunctionLike $functionLike): Type
|
||||
{
|
||||
$paramTypeInfos = $this->docBlockManipulator->getParamTypeInfos($functionLike);
|
||||
|
||||
/** @var string $paramName */
|
||||
$paramName = $this->nameResolver->getName($param->var);
|
||||
if (! isset($paramTypeInfos[$paramName])) {
|
||||
return new MixedType();
|
||||
}
|
||||
$paramName = $this->nameResolver->getName($param);
|
||||
|
||||
$fqnTypeNode = $paramTypeInfos[$paramName]->getFqnTypeNode();
|
||||
|
||||
if ($fqnTypeNode instanceof NullableType) {
|
||||
$objectType = new ObjectType((string) $fqnTypeNode->type);
|
||||
|
||||
return new UnionType([$objectType, new NullType()]);
|
||||
}
|
||||
|
||||
return new ObjectType((string) $fqnTypeNode);
|
||||
return $this->docBlockManipulator->getParamTypeByName($functionLike, $paramName);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Trait_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\Contract\NodeTypeResolverAwareInterface;
|
||||
use Rector\NodeTypeResolver\Contract\PerNodeTypeResolver\PerNodeTypeResolverInterface;
|
||||
@ -82,19 +81,7 @@ final class VariableTypeResolver implements PerNodeTypeResolverInterface, NodeTy
|
||||
}
|
||||
|
||||
// get from annotation
|
||||
$varTypeInfo = $this->docBlockManipulator->getVarTypeInfo($variableNode);
|
||||
if ($varTypeInfo === null) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
$varType = $varTypeInfo->getFqnType();
|
||||
|
||||
if ($varType) {
|
||||
// @todo this can be anything :/
|
||||
return new ObjectType($varType);
|
||||
}
|
||||
|
||||
return new MixedType();
|
||||
return $this->docBlockManipulator->getVarType($variableNode);
|
||||
}
|
||||
|
||||
public function setNodeTypeResolver(NodeTypeResolver $nodeTypeResolver): void
|
||||
|
@ -1,424 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\Php;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
use Traversable;
|
||||
|
||||
abstract class AbstractTypeInfo
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isNullable = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isAlias = false;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $types = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $typesToRemove = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fqnTypes = [];
|
||||
|
||||
/**
|
||||
* @var TypeAnalyzer
|
||||
*/
|
||||
protected $typeAnalyzer;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $iterableUnionTypes = [Traversable::class, '\Traversable', 'array'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $removedTypes = [];
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
* @param string[] $fqnTypes
|
||||
*/
|
||||
public function __construct(array $types, TypeAnalyzer $typeAnalyzer, array $fqnTypes = [])
|
||||
{
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
$this->types = $this->analyzeAndNormalizeTypes($types);
|
||||
|
||||
// fallback
|
||||
if ($fqnTypes === []) {
|
||||
$fqnTypes = $types;
|
||||
}
|
||||
|
||||
$this->fqnTypes = $this->analyzeAndNormalizeTypes($fqnTypes);
|
||||
}
|
||||
|
||||
public function isNullable(): bool
|
||||
{
|
||||
return $this->isNullable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name|NullableType|Identifier|null
|
||||
*/
|
||||
public function getFqnTypeNode()
|
||||
{
|
||||
return $this->getTypeNode(true);
|
||||
}
|
||||
|
||||
public function getTypeCount(): int
|
||||
{
|
||||
return count($this->types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name|NullableType|Identifier|null
|
||||
*/
|
||||
public function getTypeNode(bool $forceFqn = false)
|
||||
{
|
||||
if (! $this->isTypehintAble()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $this->resolveTypeForTypehint($forceFqn);
|
||||
if ($type === null) {
|
||||
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
|
||||
}
|
||||
|
||||
// normalize for type-declaration
|
||||
if (Strings::endsWith($type, '[]')) {
|
||||
$type = 'array';
|
||||
}
|
||||
|
||||
if ($this->typeAnalyzer->isPhpReservedType($type)) {
|
||||
if ($this->isNullable) {
|
||||
return new NullableType($type);
|
||||
}
|
||||
|
||||
return new Identifier($type);
|
||||
}
|
||||
|
||||
$type = ltrim($type, '\\');
|
||||
|
||||
if ($this->isAlias || $forceFqn === false) {
|
||||
$name = new Name($type);
|
||||
} else {
|
||||
$name = new FullyQualified($type);
|
||||
}
|
||||
|
||||
if ($this->isNullable) {
|
||||
return new NullableType($name);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be put as PHP typehint to code
|
||||
*/
|
||||
public function isTypehintAble(): bool
|
||||
{
|
||||
if ($this->hasRemovedTypes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// are object subtypes
|
||||
if ($this->areMutualObjectSubtypes($this->types)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$typeCount = count($this->types);
|
||||
|
||||
if ($typeCount >= 2 && $this->isArraySubtype($this->types)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $typeCount === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFqnTypes(): array
|
||||
{
|
||||
return $this->fqnTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getDocTypes(): array
|
||||
{
|
||||
$allTypes = array_merge($this->types, $this->removedTypes);
|
||||
$types = array_filter(array_unique($allTypes));
|
||||
|
||||
if ($this->isNullable) {
|
||||
$types[] = 'null';
|
||||
}
|
||||
|
||||
$types = $this->removeIterableTypeIfTraversableType($types);
|
||||
|
||||
// use mixed[] over array, that is more explicit about implicitnes
|
||||
if ($types === ['array']) {
|
||||
return ['mixed[]'];
|
||||
}
|
||||
|
||||
// remove void types, as its useless in annotation
|
||||
foreach ($types as $key => $value) {
|
||||
if ($value === 'void') {
|
||||
unset($types[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function normalizeName(string $name): string
|
||||
{
|
||||
return ltrim($name, '$');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
*/
|
||||
protected function isArraySubtype(array $types): bool
|
||||
{
|
||||
if ($types === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($types as $type) {
|
||||
if (in_array($type, ['array', 'iterable'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Strings::endsWith($type, '[]')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[] $types
|
||||
* @return string[]
|
||||
*/
|
||||
private function analyzeAndNormalizeTypes($types): array
|
||||
{
|
||||
$types = (array) $types;
|
||||
|
||||
foreach ($types as $i => $type) {
|
||||
// convert: ?Type => Type, null
|
||||
$type = $this->normalizeNullable($type);
|
||||
$type = $this->normalizeCasing($type);
|
||||
|
||||
if ($type === 'null') {
|
||||
unset($types[$i]);
|
||||
$this->isNullable = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove
|
||||
if (in_array($type, ['mixed', 'static'], true)) {
|
||||
unset($types[$i]);
|
||||
$this->removedTypes[] = $type;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($type, ['true', 'false'], true)) {
|
||||
$types[$i] = 'bool';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === '$this') {
|
||||
$types[$i] = 'self';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'object' && ! $this->typeAnalyzer->isPhpSupported('object')) {
|
||||
$this->removedTypes[] = $type;
|
||||
unset($types[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$types[$i] = $this->typeAnalyzer->normalizeType($type);
|
||||
}
|
||||
|
||||
// remove undesired types
|
||||
$types = $this->removeTypes($types);
|
||||
|
||||
$types = $this->squashTraversableAndArrayToIterable($types);
|
||||
|
||||
$types = array_unique($types);
|
||||
|
||||
// re-index to add expected behavior
|
||||
return array_values($types);
|
||||
}
|
||||
|
||||
private function hasRemovedTypes(): bool
|
||||
{
|
||||
return count($this->removedTypes) > 1;
|
||||
}
|
||||
|
||||
private function normalizeNullable(string $type): string
|
||||
{
|
||||
if (Strings::startsWith($type, '?')) {
|
||||
$type = ltrim($type, '?');
|
||||
$this->isNullable = true;
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
private function normalizeCasing(string $type): string
|
||||
{
|
||||
$types = explode('|', $type);
|
||||
foreach ($types as $key => $singleType) {
|
||||
if ($this->typeAnalyzer->isPhpReservedType($singleType) || strtolower($singleType) === '$this') {
|
||||
$types[$key] = strtolower($singleType);
|
||||
}
|
||||
}
|
||||
|
||||
return implode($types, '|');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
* @return string[]
|
||||
*/
|
||||
private function removeTypes(array $types): array
|
||||
{
|
||||
if ($this->typesToRemove === []) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
foreach ($types as $i => $type) {
|
||||
if (in_array($type, $this->typesToRemove, true)) {
|
||||
$this->removedTypes[] = $type;
|
||||
unset($types[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
* @return string[]
|
||||
*/
|
||||
private function squashTraversableAndArrayToIterable(array $types): array
|
||||
{
|
||||
// Traversable | array = iterable
|
||||
if (count(array_intersect($this->iterableUnionTypes, $types)) !== 2) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
foreach ($types as $i => $type) {
|
||||
if (in_array($type, $this->iterableUnionTypes, true)) {
|
||||
unset($types[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
$types[] = 'iterable';
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
*/
|
||||
private function areMutualObjectSubtypes(array $types): bool
|
||||
{
|
||||
return $this->resolveMutualObjectSubtype($types) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
*/
|
||||
private function resolveMutualObjectSubtype(array $types): ?string
|
||||
{
|
||||
foreach ($types as $type) {
|
||||
if ($this->classLikeExists($type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($types as $subloopType) {
|
||||
if (! is_a($subloopType, $type, true)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveTypeForTypehint(bool $forceFqn): ?string
|
||||
{
|
||||
if ($this->areMutualObjectSubtypes($this->types)) {
|
||||
return $this->resolveMutualObjectSubtype($this->types);
|
||||
}
|
||||
|
||||
if (in_array('iterable', $this->types, true)) {
|
||||
return 'iterable';
|
||||
}
|
||||
|
||||
$types = $forceFqn ? $this->fqnTypes : $this->types;
|
||||
|
||||
return $types[0];
|
||||
}
|
||||
|
||||
private function classLikeExists(string $type): bool
|
||||
{
|
||||
return ! class_exists($type) && ! interface_exists($type) && ! trait_exists($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
* @return string[]
|
||||
*/
|
||||
private function removeIterableTypeIfTraversableType(array $types): array
|
||||
{
|
||||
$hasTraversableType = false;
|
||||
|
||||
foreach ($types as $type) {
|
||||
if (Strings::endsWith($type, '[]')) {
|
||||
$hasTraversableType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $hasTraversableType) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
foreach ($types as $key => $uniqeueType) {
|
||||
// remove iterable if other types are provided
|
||||
if (in_array($uniqeueType, ['iterable', 'array'], true)) {
|
||||
unset($types[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\Php;
|
||||
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
|
||||
final class ParamTypeInfo extends AbstractTypeInfo
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $typesToRemove = ['void', 'real'];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isAlias = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
* @param string[] $fqnTypes
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
TypeAnalyzer $typeAnalyzer,
|
||||
array $types,
|
||||
array $fqnTypes = [],
|
||||
bool $isAlias = false
|
||||
) {
|
||||
$this->name = $this->normalizeName($name);
|
||||
$this->isAlias = $isAlias;
|
||||
|
||||
parent::__construct($types, $typeAnalyzer, $fqnTypes);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\Php;
|
||||
|
||||
final class ReturnTypeInfo extends AbstractTypeInfo
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $typesToRemove = ['resource', 'real'];
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\Php;
|
||||
|
||||
final class VarTypeInfo extends AbstractTypeInfo
|
||||
{
|
||||
/**
|
||||
* Callable and iterable are not property typehintable
|
||||
* @see https://wiki.php.net/rfc/typed_properties_v2#supported_types
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $typesToRemove = ['callable', 'void'];
|
||||
|
||||
public function isTypehintAble(): bool
|
||||
{
|
||||
if (count($this->types) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = $this->getType();
|
||||
if ($type === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// first letter is upper, probably class type
|
||||
if (ctype_upper($type[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->typeAnalyzer->isPhpSupported($type);
|
||||
}
|
||||
|
||||
public function getType(): ?string
|
||||
{
|
||||
return $this->types[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTypes(): array
|
||||
{
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
public function getFqnType(): ?string
|
||||
{
|
||||
return $this->fqnTypes[0] ?? null;
|
||||
}
|
||||
|
||||
public function isIterable(): bool
|
||||
{
|
||||
return $this->isArraySubtype($this->types);
|
||||
}
|
||||
}
|
@ -10,46 +10,35 @@ use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Node as PhpDocParserNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
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\Attributes\Ast\AttributeAwareNodeFactory;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareParamTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocTagNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareVarTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareIdentifierTypeNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute;
|
||||
use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterface;
|
||||
use Rector\BetterPhpDocParser\NodeDecorator\StringsTypePhpDocNodeDecorator;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
|
||||
use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter;
|
||||
use Rector\CodingStyle\Application\UseAddingCommander;
|
||||
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Exception\MissingTagException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\Php\ParamTypeInfo;
|
||||
use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
|
||||
use Rector\NodeTypeResolver\Php\VarTypeInfo;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
use Rector\PhpParser\Printer\BetterStandardPrinter;
|
||||
use Rector\TypeDeclaration\ValueObject\IdentifierValueObject;
|
||||
use Rector\PHPStan\Type\FullyQualifiedObjectType;
|
||||
use Rector\PHPStan\Type\ShortenedObjectType;
|
||||
|
||||
/**
|
||||
* @see \Rector\NodeTypeResolver\Tests\PhpDoc\NodeAnalyzer\DocBlockManipulatorTest
|
||||
@ -66,21 +55,11 @@ final class DocBlockManipulator
|
||||
*/
|
||||
private $phpDocInfoFactory;
|
||||
|
||||
/**
|
||||
* @var StringsTypePhpDocNodeDecorator
|
||||
*/
|
||||
private $stringsTypePhpDocNodeDecorator;
|
||||
|
||||
/**
|
||||
* @var PhpDocInfoPrinter
|
||||
*/
|
||||
private $phpDocInfoPrinter;
|
||||
|
||||
/**
|
||||
* @var TypeAnalyzer
|
||||
*/
|
||||
private $typeAnalyzer;
|
||||
|
||||
/**
|
||||
* @var AttributeAwareNodeFactory
|
||||
*/
|
||||
@ -91,10 +70,10 @@ final class DocBlockManipulator
|
||||
*/
|
||||
private $nodeTraverser;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $importedNames = [];
|
||||
// /**
|
||||
// * @var FullyQualifiedObjectType[]
|
||||
// */
|
||||
// private $importedNames = [];
|
||||
|
||||
/**
|
||||
* @var UseAddingCommander
|
||||
@ -116,12 +95,15 @@ final class DocBlockManipulator
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $hasPhpDocChanged = false;
|
||||
|
||||
public function __construct(
|
||||
PhpDocInfoFactory $phpDocInfoFactory,
|
||||
PhpDocInfoPrinter $phpDocInfoPrinter,
|
||||
TypeAnalyzer $typeAnalyzer,
|
||||
AttributeAwareNodeFactory $attributeAwareNodeFactory,
|
||||
StringsTypePhpDocNodeDecorator $stringsTypePhpDocNodeDecorator,
|
||||
NodeTraverser $nodeTraverser,
|
||||
NameResolver $nameResolver,
|
||||
UseAddingCommander $useAddingCommander,
|
||||
@ -130,9 +112,7 @@ final class DocBlockManipulator
|
||||
) {
|
||||
$this->phpDocInfoFactory = $phpDocInfoFactory;
|
||||
$this->phpDocInfoPrinter = $phpDocInfoPrinter;
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
$this->attributeAwareNodeFactory = $attributeAwareNodeFactory;
|
||||
$this->stringsTypePhpDocNodeDecorator = $stringsTypePhpDocNodeDecorator;
|
||||
$this->nodeTraverser = $nodeTraverser;
|
||||
$this->useAddingCommander = $useAddingCommander;
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
@ -155,7 +135,7 @@ final class DocBlockManipulator
|
||||
// advanced check, e.g. for "Namespaced\Annotations\DI"
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
|
||||
return $phpDocInfo->hasTag($name);
|
||||
return (bool) $phpDocInfo->getByType($name);
|
||||
}
|
||||
|
||||
public function addTag(Node $node, PhpDocChildNode $phpDocChildNode): void
|
||||
@ -185,24 +165,22 @@ final class DocBlockManipulator
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo, $shouldSkipEmptyLinesAbove);
|
||||
}
|
||||
|
||||
public function changeType(Node $node, string $oldType, string $newType, bool $includeChildren = false): void
|
||||
public function changeType(Node $node, Type $oldType, Type $newType): void
|
||||
{
|
||||
if (! $this->hasNodeTypeChangeableTags($node)) {
|
||||
if (! $this->hasNodeTypeTags($node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
$this->renamePhpDocType($phpDocInfo->getPhpDocNode(), $oldType, $newType, $node);
|
||||
|
||||
$this->replacePhpDocTypeByAnother($phpDocInfo->getPhpDocNode(), $oldType, $newType, $node, $includeChildren);
|
||||
if ($this->hasPhpDocChanged === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
|
||||
}
|
||||
|
||||
public function changeTypeIncludingChildren(Node $node, string $oldType, string $newType): void
|
||||
{
|
||||
$this->changeType($node, $oldType, $newType, true);
|
||||
}
|
||||
|
||||
public function replaceAnnotationInNode(Node $node, string $oldAnnotation, string $newAnnotation): void
|
||||
{
|
||||
if ($node->getDocComment() === null) {
|
||||
@ -215,61 +193,43 @@ final class DocBlockManipulator
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
|
||||
}
|
||||
|
||||
public function getReturnTypeInfo(Node $node): ?ReturnTypeInfo
|
||||
public function getReturnType(Node $node): Type
|
||||
{
|
||||
if ($node->getDocComment() === null) {
|
||||
return null;
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
$types = $phpDocInfo->getShortReturnTypes();
|
||||
if ($types === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fqnTypes = $phpDocInfo->getReturnTypes();
|
||||
|
||||
return new ReturnTypeInfo($types, $this->typeAnalyzer, $fqnTypes);
|
||||
return $phpDocInfo->getReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* With "name" as key
|
||||
*
|
||||
* @param Function_|ClassMethod|Closure $functionLike
|
||||
* @return ParamTypeInfo[]
|
||||
* @return Type[]
|
||||
*/
|
||||
public function getParamTypeInfos(FunctionLike $functionLike): array
|
||||
public function getParamTypesByName(FunctionLike $functionLike): array
|
||||
{
|
||||
if ($functionLike->getDocComment() === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($functionLike);
|
||||
$types = $phpDocInfo->getParamTagValues();
|
||||
if ($types === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fqnTypes = $phpDocInfo->getParamTagValues();
|
||||
$paramTypesByName = [];
|
||||
|
||||
$paramTypeInfos = [];
|
||||
/** @var AttributeAwareParamTagValueNode $paramTagValueNode */
|
||||
foreach ($types as $i => $paramTagValueNode) {
|
||||
$fqnParamTagValueNode = $fqnTypes[$i];
|
||||
$isAlias = $this->isAlias((string) $paramTagValueNode->type, $functionLike);
|
||||
foreach ($phpDocInfo->getParamTagValues() as $paramTagValueNode) {
|
||||
$parameterName = $paramTagValueNode->parameterName;
|
||||
|
||||
$paramTypeInfo = new ParamTypeInfo(
|
||||
$paramTagValueNode->parameterName,
|
||||
$this->typeAnalyzer,
|
||||
$paramTagValueNode->getAttribute(Attribute::TYPE_AS_ARRAY),
|
||||
$fqnParamTagValueNode->getAttribute(Attribute::RESOLVED_NAMES),
|
||||
$isAlias
|
||||
$paramTypesByName[$parameterName] = $this->staticTypeMapper->mapPHPStanPhpDocTypeToPHPStanType(
|
||||
$paramTagValueNode,
|
||||
$functionLike
|
||||
);
|
||||
|
||||
$paramTypeInfos[$paramTypeInfo->getName()] = $paramTypeInfo;
|
||||
}
|
||||
|
||||
return $paramTypeInfos;
|
||||
return $paramTypesByName;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,38 +247,30 @@ final class DocBlockManipulator
|
||||
return $phpDocInfo->getTagsByName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[]|IdentifierValueObject|IdentifierValueObject[]|Type $type
|
||||
*/
|
||||
public function changeVarTag(Node $node, $type): void
|
||||
public function changeVarTag(Node $node, Type $newType): void
|
||||
{
|
||||
$this->removeTagFromNode($node, 'var', true);
|
||||
$currentVarType = $this->getVarType($node);
|
||||
|
||||
if ($type instanceof Type) {
|
||||
$type = implode('|', $this->staticTypeMapper->mapPHPStanTypeToStrings($type));
|
||||
// make sure the tags are not identical, e.g imported class vs FQN class
|
||||
if ($this->areTypesEquals($currentVarType, $newType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addTypeSpecificTag($node, 'var', $type);
|
||||
$this->removeTagFromNode($node, 'var', true);
|
||||
$this->addTypeSpecificTag($node, 'var', $newType);
|
||||
}
|
||||
|
||||
public function addReturnTag(Node $node, string $type): void
|
||||
public function addReturnTag(Node $node, Type $newType): void
|
||||
{
|
||||
// make sure the tags are not identical, e.g imported class vs FQN class
|
||||
$returnTypeInfo = $this->getReturnTypeInfo($node);
|
||||
if ($returnTypeInfo) {
|
||||
// already added
|
||||
if ([ltrim($type, '\\')] === $returnTypeInfo->getFqnTypes()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$currentReturnType = $this->getReturnType($node);
|
||||
|
||||
$returnTypeInfo = new ReturnTypeInfo(explode('|', $type), $this->typeAnalyzer);
|
||||
if ($returnTypeInfo) {
|
||||
$type = implode('|', $returnTypeInfo->getDocTypes());
|
||||
// make sure the tags are not identical, e.g imported class vs FQN class
|
||||
if ($this->areTypesEquals($currentReturnType, $newType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->removeTagFromNode($node, 'return');
|
||||
$this->addTypeSpecificTag($node, 'return', $type);
|
||||
$this->addTypeSpecificTag($node, 'return', $newType);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -335,31 +287,30 @@ final class DocBlockManipulator
|
||||
return array_shift($foundTags);
|
||||
}
|
||||
|
||||
public function getVarTypeInfo(Node $node): ?VarTypeInfo
|
||||
public function getVarType(Node $node): Type
|
||||
{
|
||||
if ($node->getDocComment() === null) {
|
||||
return null;
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
$types = $phpDocInfo->getShortVarTypes();
|
||||
if ($types === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fqnTypes = $phpDocInfo->getVarTypes();
|
||||
|
||||
return new VarTypeInfo($types, $this->typeAnalyzer, $fqnTypes);
|
||||
return $this->createPhpDocInfoFromNode($node)->getVarType();
|
||||
}
|
||||
|
||||
public function removeTagByName(PhpDocInfo $phpDocInfo, string $tagName): void
|
||||
{
|
||||
$phpDocNode = $phpDocInfo->getPhpDocNode();
|
||||
|
||||
// A. remove class-based tag
|
||||
if (class_exists($tagName)) {
|
||||
$phpDocTagNode = $phpDocInfo->getByType($tagName);
|
||||
if ($phpDocTagNode) {
|
||||
$this->removeTagFromPhpDocNode($phpDocNode, $phpDocTagNode);
|
||||
}
|
||||
}
|
||||
|
||||
// B. remove string-based tags
|
||||
$tagName = AnnotationNaming::normalizeName($tagName);
|
||||
|
||||
$phpDocTagNodes = $phpDocInfo->getTagsByName($tagName);
|
||||
|
||||
foreach ($phpDocTagNodes as $phpDocTagNode) {
|
||||
$this->removeTagFromPhpDocNode($phpDocNode, $phpDocTagNode);
|
||||
}
|
||||
@ -406,40 +357,43 @@ final class DocBlockManipulator
|
||||
}
|
||||
}
|
||||
|
||||
public function replacePhpDocTypeByAnother(
|
||||
AttributeAwarePhpDocNode $attributeAwarePhpDocNode,
|
||||
string $oldType,
|
||||
string $newType,
|
||||
Node $node,
|
||||
bool $includeChildren = false
|
||||
): AttributeAwarePhpDocNode {
|
||||
foreach ($attributeAwarePhpDocNode->children as $phpDocChildNode) {
|
||||
if (! $phpDocChildNode instanceof PhpDocTagNode) {
|
||||
continue;
|
||||
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();
|
||||
}
|
||||
|
||||
if ($newIdentifierType instanceof IdentifierTypeNode) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
// $node->name = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($newType);
|
||||
$this->hasPhpDocChanged = true;
|
||||
return $newIdentifierType;
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
);
|
||||
|
||||
if (! $this->isTagValueNodeWithType($phpDocChildNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var VarTagValueNode|ParamTagValueNode|ReturnTagValueNode $tagValueNode */
|
||||
$tagValueNode = $phpDocChildNode->value;
|
||||
|
||||
$phpDocChildNode->value->type = $this->replaceTypeNode(
|
||||
$tagValueNode->type,
|
||||
$oldType,
|
||||
$newType,
|
||||
$includeChildren
|
||||
);
|
||||
|
||||
$this->stringsTypePhpDocNodeDecorator->decorate($attributeAwarePhpDocNode, $node);
|
||||
}
|
||||
|
||||
return $attributeAwarePhpDocNode;
|
||||
return $phpDocNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @return FullyQualifiedObjectType[]
|
||||
*/
|
||||
public function importNames(Node $node): array
|
||||
{
|
||||
@ -449,35 +403,38 @@ final class DocBlockManipulator
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
$phpDocNode = $phpDocInfo->getPhpDocNode();
|
||||
$phpParserNode = $node;
|
||||
|
||||
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (
|
||||
AttributeAwareNodeInterface $docNode
|
||||
) use ($node): AttributeAwareNodeInterface {
|
||||
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (PhpDocParserNode $docNode) use (
|
||||
$node,
|
||||
$phpParserNode
|
||||
): PhpDocParserNode {
|
||||
if (! $docNode instanceof IdentifierTypeNode) {
|
||||
return $docNode;
|
||||
}
|
||||
|
||||
// is class without namespaced name → skip
|
||||
$name = ltrim($docNode->name, '\\');
|
||||
if (! Strings::contains($name, '\\')) {
|
||||
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($docNode, $phpParserNode);
|
||||
|
||||
// already imported
|
||||
if (! $staticType instanceof FullyQualifiedObjectType) {
|
||||
return $docNode;
|
||||
}
|
||||
|
||||
$fullyQualifiedName = $this->getFullyQualifiedName($docNode);
|
||||
$shortName = $this->getShortName($name);
|
||||
|
||||
return $this->processFqnNameImport($node, $docNode, $shortName, $fullyQualifiedName);
|
||||
return $this->processFqnNameImport($node, $docNode, $staticType);
|
||||
});
|
||||
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
|
||||
if ($this->hasPhpDocChanged) {
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
|
||||
}
|
||||
|
||||
return $this->importedNames;
|
||||
return [];
|
||||
// return $this->importedNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[]|null $excludedClasses
|
||||
* @param string[] $excludedClasses
|
||||
*/
|
||||
public function changeUnderscoreType(Node $node, string $namespacePrefix, ?array $excludedClasses): void
|
||||
public function changeUnderscoreType(Node $node, string $namespacePrefix, array $excludedClasses): void
|
||||
{
|
||||
if ($node->getDocComment() === null) {
|
||||
return;
|
||||
@ -485,48 +442,58 @@ final class DocBlockManipulator
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
$phpDocNode = $phpDocInfo->getPhpDocNode();
|
||||
$phpParserNode = $node;
|
||||
|
||||
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (AttributeAwareNodeInterface $node) use (
|
||||
$this->nodeTraverser->traverseWithCallable($phpDocNode, function (PhpDocParserNode $node) use (
|
||||
$namespacePrefix,
|
||||
$excludedClasses
|
||||
): AttributeAwareNodeInterface {
|
||||
$excludedClasses,
|
||||
$phpParserNode
|
||||
): PhpDocParserNode {
|
||||
if (! $node instanceof IdentifierTypeNode) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
$name = ltrim($node->name, '\\');
|
||||
if (! Strings::startsWith($name, $namespacePrefix)) {
|
||||
$staticType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($node, $phpParserNode);
|
||||
if (! $staticType instanceof ObjectType) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
if (! Strings::startsWith($staticType->getClassName(), $namespacePrefix)) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
// excluded?
|
||||
if (is_array($excludedClasses) && in_array($name, $excludedClasses, true)) {
|
||||
if (in_array($staticType->getClassName(), $excludedClasses, true)) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
// change underscore to \\
|
||||
$nameParts = explode('_', $name);
|
||||
$nameParts = explode('_', $staticType->getClassName());
|
||||
$node->name = '\\' . implode('\\', $nameParts);
|
||||
|
||||
$this->hasPhpDocChanged = true;
|
||||
|
||||
return $node;
|
||||
});
|
||||
|
||||
if ($this->hasPhpDocChanged === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* For better performance
|
||||
*/
|
||||
public function hasNodeTypeChangeableTags(Node $node): bool
|
||||
public function hasNodeTypeTags(Node $node): bool
|
||||
{
|
||||
$docComment = $node->getDocComment();
|
||||
if ($docComment === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$text = $docComment->getText();
|
||||
|
||||
return (bool) Strings::match($text, '#\@(param|throws|return|var)\b#');
|
||||
return (bool) Strings::match($docComment->getText(), '#\@(param|throws|return|var)\b#');
|
||||
}
|
||||
|
||||
public function updateNodeWithPhpDocInfo(
|
||||
@ -564,7 +531,7 @@ final class DocBlockManipulator
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
|
||||
$relationTagValueNode = $phpDocInfo->getDoctrineRelationTagValueNode();
|
||||
$relationTagValueNode = $phpDocInfo->getByType(DoctrineRelationTagValueNodeInterface::class);
|
||||
if ($relationTagValueNode === null) {
|
||||
return null;
|
||||
}
|
||||
@ -584,209 +551,68 @@ final class DocBlockManipulator
|
||||
return $this->phpDocInfoFactory->createFromNode($node);
|
||||
}
|
||||
|
||||
public function getParamTypeByName(FunctionLike $functionLike, string $paramName): Type
|
||||
{
|
||||
$paramTypes = $this->getParamTypesByName($functionLike);
|
||||
return $paramTypes[$paramName] ?? new MixedType();
|
||||
}
|
||||
|
||||
/**
|
||||
* All class-type tags are FQN by default to keep default convention through the code.
|
||||
* Some people prefer FQN, some short. FQN can be shorten with \Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector later, while short prolonged not
|
||||
* @param string|string[]|IdentifierValueObject|IdentifierValueObject[] $type
|
||||
*/
|
||||
private function addTypeSpecificTag(Node $node, string $name, $type): void
|
||||
private function addTypeSpecificTag(Node $node, string $name, Type $type): void
|
||||
{
|
||||
if (! is_array($type)) {
|
||||
$type = [$type];
|
||||
}
|
||||
|
||||
foreach ($type as $key => $singleType) {
|
||||
// prefix possible class name
|
||||
$type[$key] = $this->preslashFullyQualifiedNames($singleType);
|
||||
}
|
||||
|
||||
$type = implode('|', $type);
|
||||
$docStringType = $this->staticTypeMapper->mapPHPStanTypeToDocString($type);
|
||||
|
||||
// there might be no phpdoc at all
|
||||
if ($node->getDocComment() !== null) {
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
$phpDocNode = $phpDocInfo->getPhpDocNode();
|
||||
|
||||
$varTagValueNode = new AttributeAwareVarTagValueNode(new AttributeAwareIdentifierTypeNode($type), '', '');
|
||||
$varTagValueNode = new AttributeAwareVarTagValueNode(new AttributeAwareIdentifierTypeNode(
|
||||
$docStringType
|
||||
), '', '');
|
||||
$phpDocNode->children[] = new AttributeAwarePhpDocTagNode('@' . $name, $varTagValueNode);
|
||||
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
|
||||
} else {
|
||||
// create completely new docblock
|
||||
$varDocComment = sprintf("/**\n * @%s %s\n */", $name, $type);
|
||||
$varDocComment = sprintf("/**\n * @%s %s\n */", $name, $docStringType);
|
||||
$node->setDocComment(new Doc($varDocComment));
|
||||
}
|
||||
}
|
||||
|
||||
private function isTagValueNodeWithType(PhpDocTagNode $phpDocTagNode): bool
|
||||
{
|
||||
return $phpDocTagNode->value instanceof ParamTagValueNode ||
|
||||
$phpDocTagNode->value instanceof VarTagValueNode ||
|
||||
$phpDocTagNode->value instanceof ReturnTagValueNode ||
|
||||
$phpDocTagNode->value instanceof ThrowsTagValueNode;
|
||||
}
|
||||
|
||||
private function replaceTypeNode(
|
||||
TypeNode $typeNode,
|
||||
string $oldType,
|
||||
string $newType,
|
||||
bool $includeChildren = false
|
||||
): TypeNode {
|
||||
// @todo use $this->nodeTraverser->traverseWithCallable here matching "AttributeAwareIdentifierTypeNode"
|
||||
|
||||
if ($typeNode instanceof AttributeAwareIdentifierTypeNode) {
|
||||
$nodeType = $this->resolveNodeType($typeNode);
|
||||
|
||||
// by default do not override subtypes, can actually use parent type (race condition), which is not desired
|
||||
// see: $includeChildren
|
||||
if (($includeChildren && is_a($nodeType, $oldType, true)) || ltrim($nodeType, '\\') === $oldType) {
|
||||
$newType = $this->forceFqnPrefix($newType);
|
||||
|
||||
return new AttributeAwareIdentifierTypeNode($newType);
|
||||
}
|
||||
}
|
||||
|
||||
if ($typeNode instanceof UnionTypeNode) {
|
||||
foreach ($typeNode->types as $key => $subTypeNode) {
|
||||
$typeNode->types[$key] = $this->replaceTypeNode($subTypeNode, $oldType, $newType, $includeChildren);
|
||||
}
|
||||
}
|
||||
|
||||
if ($typeNode instanceof ArrayTypeNode) {
|
||||
$typeNode->type = $this->replaceTypeNode($typeNode->type, $oldType, $newType, $includeChildren);
|
||||
|
||||
return $typeNode;
|
||||
}
|
||||
|
||||
return $typeNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttributeAwareNodeInterface&TypeNode $typeNode
|
||||
*/
|
||||
private function resolveNodeType(TypeNode $typeNode): string
|
||||
{
|
||||
$nodeType = $typeNode->getAttribute(Attribute::RESOLVED_NAME);
|
||||
|
||||
if ($nodeType === null) {
|
||||
$nodeType = $typeNode->getAttribute(Attribute::TYPE_AS_STRING);
|
||||
}
|
||||
|
||||
if ($nodeType === null) {
|
||||
$nodeType = $typeNode->name;
|
||||
}
|
||||
|
||||
return $nodeType;
|
||||
}
|
||||
|
||||
private function forceFqnPrefix(string $newType): string
|
||||
{
|
||||
if (Strings::contains($newType, '\\')) {
|
||||
$newType = '\\' . ltrim($newType, '\\');
|
||||
}
|
||||
|
||||
return $newType;
|
||||
}
|
||||
|
||||
private function getShortName(string $name): string
|
||||
{
|
||||
return Strings::after($name, '\\', -1) ?: $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttributeAwareNodeInterface|AttributeAwareIdentifierTypeNode $attributeAwareNode
|
||||
*/
|
||||
private function getFullyQualifiedName(AttributeAwareNodeInterface $attributeAwareNode): string
|
||||
{
|
||||
if ($attributeAwareNode->getAttribute(Attribute::RESOLVED_NAME)) {
|
||||
$fqnName = $attributeAwareNode->getAttribute(Attribute::RESOLVED_NAME);
|
||||
} else {
|
||||
$fqnName = $attributeAwareNode->getAttribute(Attribute::RESOLVED_NAMES)[0] ?? $attributeAwareNode->name;
|
||||
}
|
||||
|
||||
return ltrim($fqnName, '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttributeAwareIdentifierTypeNode $attributeAwareNode
|
||||
*/
|
||||
private function processFqnNameImport(
|
||||
Node $node,
|
||||
AttributeAwareNodeInterface $attributeAwareNode,
|
||||
string $shortName,
|
||||
string $fullyQualifiedName
|
||||
): AttributeAwareNodeInterface {
|
||||
// the name is already in the same namespace implicitly
|
||||
$namespaceName = $node->getAttribute(AttributeKey::NAMESPACE_NAME);
|
||||
|
||||
// the class in the same namespace as different file can se used in this code, the short names would colide → skip
|
||||
$currentNamespaceShortName = $namespaceName . '\\' . $shortName;
|
||||
|
||||
if (class_exists($currentNamespaceShortName)) {
|
||||
if ($currentNamespaceShortName !== $fullyQualifiedName) {
|
||||
if ($this->isCurrentNamespaceSameShortClassAlreadyUsed(
|
||||
$node,
|
||||
$currentNamespaceShortName,
|
||||
$shortName
|
||||
)) {
|
||||
return $attributeAwareNode;
|
||||
}
|
||||
}
|
||||
IdentifierTypeNode $identifierTypeNode,
|
||||
FullyQualifiedObjectType $fullyQualifiedObjectType
|
||||
): PhpDocParserNode {
|
||||
// nothing to be changed → skip
|
||||
if ($this->hasTheSameShortClassInCurrentNamespace($node, $fullyQualifiedObjectType)) {
|
||||
return $identifierTypeNode;
|
||||
}
|
||||
|
||||
if ($this->useAddingCommander->isShortImported($node, $fullyQualifiedName)) {
|
||||
if ($this->useAddingCommander->isImportShortable($node, $fullyQualifiedName)) {
|
||||
$attributeAwareNode->name = $shortName;
|
||||
if ($this->useAddingCommander->isShortImported($node, $fullyQualifiedObjectType)) {
|
||||
if ($this->useAddingCommander->isImportShortable($node, $fullyQualifiedObjectType)) {
|
||||
$identifierTypeNode->name = $fullyQualifiedObjectType->getShortName();
|
||||
$this->hasPhpDocChanged = true;
|
||||
}
|
||||
|
||||
return $attributeAwareNode;
|
||||
return $identifierTypeNode;
|
||||
}
|
||||
|
||||
$attributeAwareNode->name = $shortName;
|
||||
$this->useAddingCommander->addUseImport($node, $fullyQualifiedName);
|
||||
$identifierTypeNode->name = $fullyQualifiedObjectType->getShortName();
|
||||
$this->hasPhpDocChanged = true;
|
||||
$this->useAddingCommander->addUseImport($node, $fullyQualifiedObjectType);
|
||||
|
||||
return $attributeAwareNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|IdentifierValueObject $type
|
||||
*/
|
||||
private function preslashFullyQualifiedNames($type): string
|
||||
{
|
||||
if ($type instanceof IdentifierValueObject) {
|
||||
if ($type->isAlias()) {
|
||||
return $type->getName();
|
||||
}
|
||||
|
||||
$type = $type->getName();
|
||||
}
|
||||
|
||||
$joinChar = '|'; // default
|
||||
if (Strings::contains($type, '|')) { // intersection
|
||||
$joinChar = '|';
|
||||
$types = explode($joinChar, $type);
|
||||
} elseif (Strings::contains($type, '&')) { // union
|
||||
$joinChar = '&';
|
||||
$types = explode($joinChar, $type);
|
||||
} else {
|
||||
$types = [$type];
|
||||
}
|
||||
|
||||
foreach ($types as $key => $singleType) {
|
||||
if ($this->typeAnalyzer->isPhpReservedType($singleType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$types[$key] = '\\' . ltrim($singleType, '\\');
|
||||
}
|
||||
|
||||
return implode($joinChar, $types);
|
||||
return $identifierTypeNode;
|
||||
}
|
||||
|
||||
private function isCurrentNamespaceSameShortClassAlreadyUsed(
|
||||
Node $node,
|
||||
string $fullyQualifiedName,
|
||||
string $shortName
|
||||
ShortenedObjectType $shortenedObjectType
|
||||
): bool {
|
||||
/** @var ClassLike|null $classNode */
|
||||
$classNode = $node->getAttribute(AttributeKey::CLASS_NODE);
|
||||
@ -797,8 +623,8 @@ final class DocBlockManipulator
|
||||
|
||||
$className = $this->nameResolver->getName($classNode);
|
||||
|
||||
if (isset($this->usedShortNameByClasses[$className][$shortName])) {
|
||||
return $this->usedShortNameByClasses[$className][$shortName];
|
||||
if (isset($this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()])) {
|
||||
return $this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()];
|
||||
}
|
||||
|
||||
$printedClass = $this->betterStandardPrinter->print($classNode->stmts);
|
||||
@ -806,37 +632,58 @@ final class DocBlockManipulator
|
||||
// short with space " Type"| fqn
|
||||
$shortNameOrFullyQualifiedNamePattern = sprintf(
|
||||
'#(\s%s\b|\b%s\b)#',
|
||||
preg_quote($shortName),
|
||||
preg_quote($shortenedObjectType->getShortName()),
|
||||
preg_quote($fullyQualifiedName)
|
||||
);
|
||||
|
||||
$isShortClassUsed = (bool) Strings::match($printedClass, $shortNameOrFullyQualifiedNamePattern);
|
||||
|
||||
$this->usedShortNameByClasses[$className][$shortName] = $isShortClassUsed;
|
||||
$this->usedShortNameByClasses[$className][$shortenedObjectType->getShortName()] = $isShortClassUsed;
|
||||
|
||||
return $isShortClassUsed;
|
||||
}
|
||||
|
||||
private function isAlias(string $paramType, Node $node): bool
|
||||
private function areTypesEquals(Type $firstType, Type $secondType): bool
|
||||
{
|
||||
/** @var Use_[]|null $useNodes */
|
||||
$useNodes = $node->getAttribute(AttributeKey::USE_NODES);
|
||||
if ($useNodes === null) {
|
||||
return $this->staticTypeMapper->createTypeHash($firstType) === $this->staticTypeMapper->createTypeHash(
|
||||
$secondType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ($this->doesClassLikeExist($currentNamespaceShortName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($useNodes as $useNode) {
|
||||
foreach ($useNode->uses as $useUse) {
|
||||
if ($useUse->alias === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((string) $useUse->alias === $paramType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ($currentNamespaceShortName === $fullyQualifiedObjectType->getClassName()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->isCurrentNamespaceSameShortClassAlreadyUsed(
|
||||
$node,
|
||||
$currentNamespaceShortName,
|
||||
$fullyQualifiedObjectType->getShortNameType()
|
||||
);
|
||||
}
|
||||
|
||||
private function doesClassLikeExist(string $classLike): bool
|
||||
{
|
||||
return class_exists($classLike) || interface_exists($classLike) || trait_exists($classLike);
|
||||
}
|
||||
}
|
||||
|
@ -1,148 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use Rector\BetterPhpDocParser\Ast\NodeTraverser;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareParamTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareReturnTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareThrowsTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareVarTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareArrayTypeNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareGenericTypeNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareIdentifierTypeNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareIntersectionTypeNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareThisTypeNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareUnionTypeNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute;
|
||||
use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterface;
|
||||
use Rector\BetterPhpDocParser\Contract\PhpDocNodeDecoratorInterface;
|
||||
|
||||
final class FqnNamePhpDocNodeDecorator implements PhpDocNodeDecoratorInterface
|
||||
{
|
||||
/**
|
||||
* @var NamespaceAnalyzer
|
||||
*/
|
||||
private $namespaceAnalyzer;
|
||||
|
||||
/**
|
||||
* @var NodeTraverser
|
||||
*/
|
||||
private $nodeTraverser;
|
||||
|
||||
public function __construct(NamespaceAnalyzer $namespaceAnalyzer, NodeTraverser $nodeTraverser)
|
||||
{
|
||||
$this->namespaceAnalyzer = $namespaceAnalyzer;
|
||||
$this->nodeTraverser = $nodeTraverser;
|
||||
}
|
||||
|
||||
public function decorate(AttributeAwarePhpDocNode $attributeAwarePhpDocNode, Node $node): AttributeAwarePhpDocNode
|
||||
{
|
||||
$this->nodeTraverser->traverseWithCallable(
|
||||
$attributeAwarePhpDocNode,
|
||||
function (AttributeAwareNodeInterface $attributeAwarePhpDocNode) use ($node): AttributeAwareNodeInterface {
|
||||
if (! $attributeAwarePhpDocNode instanceof IdentifierTypeNode) {
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
|
||||
if (! $this->isClassyType($attributeAwarePhpDocNode->name)) {
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
|
||||
$fqnName = $this->namespaceAnalyzer->resolveTypeToFullyQualified(
|
||||
$attributeAwarePhpDocNode->name,
|
||||
$node
|
||||
);
|
||||
|
||||
$attributeAwarePhpDocNode->setAttribute(Attribute::RESOLVED_NAME, $fqnName);
|
||||
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
);
|
||||
|
||||
// collect to particular node types
|
||||
$this->nodeTraverser->traverseWithCallable(
|
||||
$attributeAwarePhpDocNode,
|
||||
function (AttributeAwareNodeInterface $attributeAwarePhpDocNode): AttributeAwareNodeInterface {
|
||||
if (! $this->isTypeAwareNode($attributeAwarePhpDocNode)) {
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
|
||||
/** @var AttributeAwareVarTagValueNode $attributeAwarePhpDocNode */
|
||||
$resolvedNames = $this->collectResolvedNames($attributeAwarePhpDocNode->type);
|
||||
$attributeAwarePhpDocNode->setAttribute(Attribute::RESOLVED_NAMES, $resolvedNames);
|
||||
|
||||
/** @var AttributeAwareNodeInterface $attributeAwaretType */
|
||||
$attributeAwaretType = $attributeAwarePhpDocNode->type;
|
||||
$attributeAwaretType->setAttribute(Attribute::RESOLVED_NAMES, $resolvedNames);
|
||||
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
);
|
||||
|
||||
return $attributeAwarePhpDocNode;
|
||||
}
|
||||
|
||||
private function isClassyType(string $name): bool
|
||||
{
|
||||
return ctype_upper($name[0]);
|
||||
}
|
||||
|
||||
private function isTypeAwareNode(AttributeAwareNodeInterface $attributeAwareNode): bool
|
||||
{
|
||||
return $attributeAwareNode instanceof AttributeAwareVarTagValueNode ||
|
||||
$attributeAwareNode instanceof AttributeAwareParamTagValueNode ||
|
||||
$attributeAwareNode instanceof AttributeAwareReturnTagValueNode ||
|
||||
$attributeAwareNode instanceof AttributeAwareThrowsTagValueNode ||
|
||||
$attributeAwareNode instanceof AttributeAwareGenericTypeNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function collectResolvedNames(TypeNode $typeNode): array
|
||||
{
|
||||
$resolvedNames = [];
|
||||
if ($typeNode instanceof AttributeAwareUnionTypeNode || $typeNode instanceof AttributeAwareIntersectionTypeNode) {
|
||||
foreach ($typeNode->types as $subtype) {
|
||||
$resolvedNames = array_merge($resolvedNames, $this->collectResolvedNames($subtype));
|
||||
}
|
||||
} elseif ($typeNode instanceof AttributeAwareThisTypeNode) {
|
||||
$resolvedNames[] = $typeNode->getAttribute(Attribute::TYPE_AS_STRING);
|
||||
} elseif ($typeNode instanceof AttributeAwareArrayTypeNode) {
|
||||
$resolved = false;
|
||||
if ($typeNode->type instanceof AttributeAwareIdentifierTypeNode) {
|
||||
$resolvedType = $this->resolveIdentifierType($typeNode->type);
|
||||
if ($resolvedType) {
|
||||
$resolvedNames[] = $resolvedType . '[]';
|
||||
$resolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($resolved === false) {
|
||||
$resolvedNames[] = $typeNode->getAttribute(Attribute::TYPE_AS_STRING);
|
||||
}
|
||||
} elseif ($typeNode instanceof AttributeAwareIdentifierTypeNode) {
|
||||
$resolvedType = $this->resolveIdentifierType($typeNode);
|
||||
if ($resolvedType) {
|
||||
$resolvedNames[] = $resolvedType;
|
||||
}
|
||||
}
|
||||
|
||||
return array_filter($resolvedNames);
|
||||
}
|
||||
|
||||
private function resolveIdentifierType(AttributeAwareIdentifierTypeNode $attributeAwareIdentifierTypeNode): ?string
|
||||
{
|
||||
if ($attributeAwareIdentifierTypeNode->getAttribute(Attribute::RESOLVED_NAME)) {
|
||||
return $attributeAwareIdentifierTypeNode->getAttribute(Attribute::RESOLVED_NAME);
|
||||
} elseif ($attributeAwareIdentifierTypeNode->getAttribute(Attribute::TYPE_AS_STRING)) {
|
||||
return $attributeAwareIdentifierTypeNode->getAttribute(Attribute::TYPE_AS_STRING);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
|
||||
final class NamespaceAnalyzer
|
||||
{
|
||||
/**
|
||||
* @var TypeAnalyzer
|
||||
*/
|
||||
private $typeAnalyzer;
|
||||
|
||||
public function __construct(TypeAnalyzer $typeAnalyzer)
|
||||
{
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
}
|
||||
|
||||
public function resolveTypeToFullyQualified(string $type, Node $node): string
|
||||
{
|
||||
$useNodes = $node->getAttribute(AttributeKey::USE_NODES);
|
||||
if ($useNodes === null) {
|
||||
$useNodes = [];
|
||||
}
|
||||
|
||||
$useStatementMatch = $this->matchUseStatements($type, $useNodes);
|
||||
if ($useStatementMatch) {
|
||||
return $useStatementMatch;
|
||||
}
|
||||
|
||||
if ($this->typeAnalyzer->isPhpReservedType($type)) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
// return \absolute values without prefixing
|
||||
if (Strings::startsWith($type, '\\')) {
|
||||
return ltrim($type, '\\');
|
||||
}
|
||||
|
||||
$namespace = $node->getAttribute(AttributeKey::NAMESPACE_NAME);
|
||||
|
||||
return ($namespace ? $namespace . '\\' : '') . $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Use_[] $useNodes
|
||||
*/
|
||||
private function matchUseStatements(string $type, array $useNodes): ?string
|
||||
{
|
||||
foreach ($useNodes as $useNode) {
|
||||
$useUseNode = $useNode->uses[0];
|
||||
if ($useUseNode->alias) {
|
||||
$nodeUseName = $useUseNode->alias->name;
|
||||
} else {
|
||||
$nodeUseName = $useUseNode->name->toString();
|
||||
}
|
||||
|
||||
if (Strings::endsWith($nodeUseName, '\\' . $type)) {
|
||||
return $nodeUseName;
|
||||
}
|
||||
|
||||
// exactly the same
|
||||
if ($type === $useUseNode->name->toString()) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
// alias
|
||||
if ($type === $useUseNode->getAlias()->toString()) {
|
||||
return $nodeUseName;
|
||||
}
|
||||
|
||||
// Some\Start <=> Start\End
|
||||
$nodeUseNameParts = explode('\\', $nodeUseName);
|
||||
$typeParts = explode('\\', $type);
|
||||
|
||||
$lastNodeUseNamePart = array_pop($nodeUseNameParts);
|
||||
$firstTypePart = array_shift($typeParts);
|
||||
|
||||
if ($lastNodeUseNamePart === $firstTypePart) {
|
||||
return sprintf(
|
||||
'%s\%s\%s',
|
||||
implode('\\', $nodeUseNameParts),
|
||||
$lastNodeUseNamePart,
|
||||
implode('\\', $typeParts)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -5,41 +5,55 @@ namespace Rector\NodeTypeResolver;
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar\DNumber;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\BooleanType;
|
||||
use PHPStan\Type\CallableType;
|
||||
use PHPStan\Type\ClosureType;
|
||||
use PHPStan\Type\Constant\ConstantArrayType;
|
||||
use PHPStan\Type\FloatType;
|
||||
use PHPStan\Type\IntegerType;
|
||||
use PHPStan\Type\IntersectionType;
|
||||
use PHPStan\Type\IterableType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\NeverType;
|
||||
use PHPStan\Type\NullType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\ObjectWithoutClassType;
|
||||
use PHPStan\Type\ResourceType;
|
||||
use PHPStan\Type\StaticType;
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\ThisType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeWithClassName;
|
||||
use PHPStan\Type\UnionType;
|
||||
use PHPStan\Type\VoidType;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareUnionTypeNode;
|
||||
use Rector\BetterPhpDocParser\Type\PreSlashStringType;
|
||||
use Rector\Exception\NotImplementedException;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
||||
use Rector\Php\PhpVersionProvider;
|
||||
use Rector\PhpParser\Node\Manipulator\ConstFetchManipulator;
|
||||
use Rector\PHPStan\Type\AliasedObjectType;
|
||||
use Rector\PHPStan\Type\FullyQualifiedObjectType;
|
||||
use Rector\PHPStan\Type\ParentStaticType;
|
||||
use Rector\PHPStan\Type\SelfObjectType;
|
||||
use Rector\PHPStan\Type\ShortenedObjectType;
|
||||
use Rector\TypeDeclaration\PHPStan\Type\ObjectTypeSpecifier;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Maps PhpParser <=> PHPStan <=> PHPStan doc <=> string type nodes to between all possible formats
|
||||
@ -51,138 +65,54 @@ final class StaticTypeMapper
|
||||
*/
|
||||
private const PHP_VERSION_SCALAR_TYPES = '7.0';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const PHP_VERSION_VOID_TYPE = '7.1';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const PHP_VERSION_OBJECT_TYPE = '7.2';
|
||||
|
||||
/**
|
||||
* @var PhpVersionProvider
|
||||
*/
|
||||
private $phpVersionProvider;
|
||||
|
||||
/**
|
||||
* @var ConstFetchManipulator
|
||||
* @var TypeFactory
|
||||
*/
|
||||
private $constFetchManipulator;
|
||||
private $typeFactory;
|
||||
|
||||
/**
|
||||
* @var ObjectTypeSpecifier
|
||||
*/
|
||||
private $objectTypeSpecifier;
|
||||
|
||||
public function __construct(
|
||||
PhpVersionProvider $phpVersionProvider,
|
||||
ConstFetchManipulator $constFetchManipulator
|
||||
TypeFactory $typeFactory,
|
||||
ObjectTypeSpecifier $objectTypeSpecifier
|
||||
) {
|
||||
$this->phpVersionProvider = $phpVersionProvider;
|
||||
$this->constFetchManipulator = $constFetchManipulator;
|
||||
$this->typeFactory = $typeFactory;
|
||||
$this->objectTypeSpecifier = $objectTypeSpecifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo this should return only single string
|
||||
* @return string[]
|
||||
*/
|
||||
public function mapPHPStanTypeToStrings(Type $currentPHPStanType, bool $preslashObjectType = false): array
|
||||
public function mapPHPStanTypeToPHPStanPhpDocTypeNode(Type $phpStanType): ?TypeNode
|
||||
{
|
||||
if ($currentPHPStanType instanceof ObjectType) {
|
||||
$className = $currentPHPStanType->getClassName();
|
||||
if ($preslashObjectType) {
|
||||
$className = '\\' . $className;
|
||||
}
|
||||
|
||||
return [$className];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof IntegerType) {
|
||||
return ['int'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ObjectWithoutClassType) {
|
||||
return ['object'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ClosureType) {
|
||||
return ['callable'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof CallableType) {
|
||||
return ['callable'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof FloatType) {
|
||||
return ['float'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof BooleanType) {
|
||||
return ['bool'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof StringType) {
|
||||
return ['string'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof NullType) {
|
||||
return ['null'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof MixedType) {
|
||||
return ['mixed'];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ConstantArrayType) {
|
||||
return $this->resolveConstantArrayType($currentPHPStanType);
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ArrayType) {
|
||||
$types = $this->mapPHPStanTypeToStrings($currentPHPStanType->getItemType());
|
||||
|
||||
if ($types === []) {
|
||||
return ['array'];
|
||||
}
|
||||
|
||||
foreach ($types as $key => $type) {
|
||||
$types[$key] = $type . '[]';
|
||||
}
|
||||
|
||||
return array_unique($types);
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof UnionType) {
|
||||
$types = [];
|
||||
foreach ($currentPHPStanType->getTypes() as $singleStaticType) {
|
||||
$currentIterationTypes = $this->mapPHPStanTypeToStrings($singleStaticType);
|
||||
$types = array_merge($types, $currentIterationTypes);
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof IntersectionType) {
|
||||
$types = [];
|
||||
foreach ($currentPHPStanType->getTypes() as $singleStaticType) {
|
||||
$currentIterationTypes = $this->mapPHPStanTypeToStrings($singleStaticType);
|
||||
$types = array_merge($types, $currentIterationTypes);
|
||||
}
|
||||
|
||||
return $this->removeGenericArrayTypeIfThereIsSpecificArrayType($types);
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof NeverType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ThisType) {
|
||||
// @todo what is desired return value?
|
||||
return [$currentPHPStanType->getClassName()];
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public function mapPHPStanTypeToPHPStanPhpDocTypeNode(Type $currentPHPStanType): ?TypeNode
|
||||
{
|
||||
if ($currentPHPStanType instanceof UnionType) {
|
||||
if ($phpStanType instanceof UnionType) {
|
||||
$unionTypesNodes = [];
|
||||
foreach ($currentPHPStanType->getTypes() as $unionedType) {
|
||||
foreach ($phpStanType->getTypes() as $unionedType) {
|
||||
$unionTypesNodes[] = $this->mapPHPStanTypeToPHPStanPhpDocTypeNode($unionedType);
|
||||
}
|
||||
|
||||
return new AttributeAwareUnionTypeNode($unionTypesNodes);
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ArrayType) {
|
||||
$itemTypeNode = $this->mapPHPStanTypeToPHPStanPhpDocTypeNode($currentPHPStanType->getItemType());
|
||||
if ($phpStanType instanceof ArrayType) {
|
||||
$itemTypeNode = $this->mapPHPStanTypeToPHPStanPhpDocTypeNode($phpStanType->getItemType());
|
||||
if ($itemTypeNode === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
@ -190,27 +120,48 @@ final class StaticTypeMapper
|
||||
return new ArrayTypeNode($itemTypeNode);
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof IntegerType) {
|
||||
if ($phpStanType instanceof IntegerType) {
|
||||
return new IdentifierTypeNode('int');
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof StringType) {
|
||||
if ($phpStanType instanceof StringType) {
|
||||
return new IdentifierTypeNode('string');
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof FloatType) {
|
||||
if ($phpStanType instanceof FloatType) {
|
||||
return new IdentifierTypeNode('float');
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($currentPHPStanType));
|
||||
if ($phpStanType instanceof ObjectType) {
|
||||
return new IdentifierTypeNode('\\' . $phpStanType->getClassName());
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Identifier|Name|NullableType|null
|
||||
*/
|
||||
public function mapPHPStanTypeToPhpParserNode(Type $currentPHPStanType): ?Node
|
||||
public function mapPHPStanTypeToPhpParserNode(Type $phpStanType, ?string $kind = null): ?Node
|
||||
{
|
||||
if ($currentPHPStanType instanceof IntegerType) {
|
||||
if ($phpStanType instanceof VoidType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_VOID_TYPE)) {
|
||||
if (in_array($kind, ['param', 'property'], true)) {
|
||||
// param cannot be void
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Identifier('void');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof SelfObjectType) {
|
||||
return new Identifier('self');
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof IntegerType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_SCALAR_TYPES)) {
|
||||
return new Identifier('int');
|
||||
}
|
||||
@ -218,7 +169,7 @@ final class StaticTypeMapper
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof StringType) {
|
||||
if ($phpStanType instanceof StringType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_SCALAR_TYPES)) {
|
||||
return new Identifier('string');
|
||||
}
|
||||
@ -226,7 +177,7 @@ final class StaticTypeMapper
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof BooleanType) {
|
||||
if ($phpStanType instanceof BooleanType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_SCALAR_TYPES)) {
|
||||
return new Identifier('bool');
|
||||
}
|
||||
@ -234,7 +185,7 @@ final class StaticTypeMapper
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof FloatType) {
|
||||
if ($phpStanType instanceof FloatType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_SCALAR_TYPES)) {
|
||||
return new Identifier('float');
|
||||
}
|
||||
@ -242,83 +193,140 @@ final class StaticTypeMapper
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ArrayType) {
|
||||
if ($phpStanType instanceof ArrayType) {
|
||||
return new Identifier('array');
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ObjectType) {
|
||||
return new FullyQualified($currentPHPStanType->getClassName());
|
||||
if ($phpStanType instanceof IterableType) {
|
||||
return new Identifier('iterable');
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof UnionType) {
|
||||
if ($phpStanType instanceof ThisType) {
|
||||
return new Identifier('self');
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof ParentStaticType) {
|
||||
return new Identifier('parent');
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof StaticType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof MixedType) {
|
||||
if ($phpStanType instanceof CallableType || $phpStanType instanceof ClosureType) {
|
||||
if ($kind === 'property') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Identifier('callable');
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof ShortenedObjectType) {
|
||||
return new FullyQualified($phpStanType->getFullyQualifiedName());
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof AliasedObjectType) {
|
||||
return new Name($phpStanType->getClassName());
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof TypeWithClassName) {
|
||||
$lowerCasedClassName = strtolower($phpStanType->getClassName());
|
||||
if ($lowerCasedClassName === 'callable') {
|
||||
return new Identifier('callable');
|
||||
}
|
||||
|
||||
if ($lowerCasedClassName === 'self') {
|
||||
return new Identifier('self');
|
||||
}
|
||||
|
||||
if ($lowerCasedClassName === 'static') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($lowerCasedClassName === 'mixed') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FullyQualified($phpStanType->getClassName());
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof UnionType) {
|
||||
// match array types
|
||||
$arrayNode = $this->matchArrayTypes($phpStanType);
|
||||
if ($arrayNode) {
|
||||
return $arrayNode;
|
||||
}
|
||||
|
||||
// special case for nullable
|
||||
$nullabledType = $this->matchTypeForNullableUnionType($phpStanType);
|
||||
if ($nullabledType === null) {
|
||||
// use first unioned type in case of unioned object types
|
||||
return $this->matchTypeForUnionedObjectTypes($phpStanType);
|
||||
}
|
||||
|
||||
$nullabledTypeNode = $this->mapPHPStanTypeToPhpParserNode($nullabledType);
|
||||
if ($nullabledTypeNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($nullabledTypeNode instanceof NullableType) {
|
||||
return $nullabledTypeNode;
|
||||
}
|
||||
|
||||
return new NullableType($nullabledTypeNode);
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof VoidType || $phpStanType instanceof MixedType || $phpStanType instanceof ResourceType || $phpStanType instanceof NullType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($currentPHPStanType));
|
||||
if ($phpStanType instanceof ObjectWithoutClassType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_OBJECT_TYPE)) {
|
||||
return new Identifier('object');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType));
|
||||
}
|
||||
|
||||
public function mapPhpParserNodeToString(Expr $expr): string
|
||||
{
|
||||
if ($expr instanceof LNumber) {
|
||||
return 'int';
|
||||
}
|
||||
|
||||
if ($expr instanceof Array_) {
|
||||
return 'mixed[]';
|
||||
}
|
||||
|
||||
if ($expr instanceof DNumber) {
|
||||
return 'float';
|
||||
}
|
||||
|
||||
/** @var Scope $scope */
|
||||
$scope = $expr->getAttribute(AttributeKey::SCOPE);
|
||||
$exprStaticType = $scope->getType($expr);
|
||||
|
||||
if ($exprStaticType instanceof IntegerType) {
|
||||
return 'int';
|
||||
}
|
||||
|
||||
if ($exprStaticType instanceof StringType) {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
if ($this->constFetchManipulator->isBool($expr)) {
|
||||
return 'bool';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function mapPHPStanTypeToDocString(Type $phpStanType): string
|
||||
public function mapPHPStanTypeToDocString(Type $phpStanType, ?Type $parentType = null): string
|
||||
{
|
||||
if ($phpStanType instanceof UnionType) {
|
||||
$stringTypes = [];
|
||||
|
||||
foreach ($phpStanType->getTypes() as $unionedType) {
|
||||
if ($unionedType instanceof ObjectType) {
|
||||
$stringTypes[] = $this->mapPHPStanTypeToDocString($unionedType);
|
||||
}
|
||||
$stringTypes[] = $this->mapPHPStanTypeToDocString($unionedType);
|
||||
}
|
||||
|
||||
// remove empty values, e.g. void/iterable
|
||||
$stringTypes = array_unique($stringTypes);
|
||||
$stringTypes = array_filter($stringTypes);
|
||||
|
||||
return implode('|', $stringTypes);
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof AliasedObjectType) {
|
||||
// no preslash for alias
|
||||
return $phpStanType->getClassName();
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof ShortenedObjectType) {
|
||||
// no preslash for alias
|
||||
return $phpStanType->getFullyQualifiedName();
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof FullyQualifiedObjectType) {
|
||||
// always prefixed with \\
|
||||
return '\\' . $phpStanType->getClassName();
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof ObjectType) {
|
||||
if (class_exists($phpStanType->getClassName()) || interface_exists(
|
||||
$phpStanType->getClassName()
|
||||
) || trait_exists($phpStanType->getClassName())) {
|
||||
if ($this->isExistingClassLike($phpStanType->getClassName())) {
|
||||
return '\\' . $phpStanType->getClassName();
|
||||
}
|
||||
|
||||
return $phpStanType->getClassName();
|
||||
}
|
||||
|
||||
@ -326,53 +334,421 @@ final class StaticTypeMapper
|
||||
return 'string';
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function resolveConstantArrayType(ConstantArrayType $constantArrayType): array
|
||||
{
|
||||
$arrayTypes = [];
|
||||
|
||||
foreach ($constantArrayType->getValueTypes() as $valueType) {
|
||||
$arrayTypes = array_merge($arrayTypes, $this->mapPHPStanTypeToStrings($valueType));
|
||||
if ($phpStanType instanceof IntegerType) {
|
||||
return 'int';
|
||||
}
|
||||
|
||||
$arrayTypes = array_unique($arrayTypes);
|
||||
if ($phpStanType instanceof NullType) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
return array_map(function (string $arrayType): string {
|
||||
return $arrayType . '[]';
|
||||
}, $arrayTypes);
|
||||
if ($phpStanType instanceof ArrayType) {
|
||||
if ($phpStanType->getItemType() instanceof UnionType) {
|
||||
$unionedTypesAsString = [];
|
||||
foreach ($phpStanType->getItemType()->getTypes() as $unionedArrayItemType) {
|
||||
$unionedTypesAsString[] = $this->mapPHPStanTypeToDocString(
|
||||
$unionedArrayItemType,
|
||||
$phpStanType
|
||||
) . '[]';
|
||||
}
|
||||
|
||||
$unionedTypesAsString = array_values($unionedTypesAsString);
|
||||
$unionedTypesAsString = array_unique($unionedTypesAsString);
|
||||
|
||||
return implode('|', $unionedTypesAsString);
|
||||
}
|
||||
|
||||
$docString = $this->mapPHPStanTypeToDocString($phpStanType->getItemType(), $parentType);
|
||||
|
||||
// @todo improve this
|
||||
$docStringTypes = explode('|', $docString);
|
||||
$docStringTypes = array_filter($docStringTypes);
|
||||
|
||||
foreach ($docStringTypes as $key => $docStringType) {
|
||||
$docStringTypes[$key] = $docStringType . '[]';
|
||||
}
|
||||
|
||||
return implode('|', $docStringTypes);
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof MixedType) {
|
||||
return 'mixed';
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof FloatType) {
|
||||
return 'float';
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof VoidType) {
|
||||
if ($this->phpVersionProvider->isAtLeast('7.1')) {
|
||||
// the void type is better done in PHP code
|
||||
return '';
|
||||
}
|
||||
|
||||
// fallback for PHP 7.0 and older, where void type was only in docs
|
||||
return 'void';
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof BooleanType) {
|
||||
return 'bool';
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof IterableType) {
|
||||
if ($this->phpVersionProvider->isAtLeast('7.1')) {
|
||||
// the void type is better done in PHP code
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'iterable';
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof NeverType) {
|
||||
return 'mixed';
|
||||
}
|
||||
|
||||
if ($phpStanType instanceof CallableType) {
|
||||
return 'callable';
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes "array" if there is "SomeType[]" already
|
||||
*
|
||||
* @param string[] $types
|
||||
* @return string[]
|
||||
*/
|
||||
private function removeGenericArrayTypeIfThereIsSpecificArrayType(array $types): array
|
||||
public function mapPhpParserNodePHPStanType(Node $node): Type
|
||||
{
|
||||
$hasSpecificArrayType = false;
|
||||
foreach ($types as $key => $type) {
|
||||
if (Strings::endsWith($type, '[]')) {
|
||||
$hasSpecificArrayType = true;
|
||||
break;
|
||||
if ($node instanceof Expr) {
|
||||
/** @var Scope $scope */
|
||||
$scope = $node->getAttribute(AttributeKey::SCOPE);
|
||||
|
||||
return $scope->getType($node);
|
||||
}
|
||||
|
||||
if ($node instanceof NullableType) {
|
||||
if ($node->type instanceof FullyQualified) {
|
||||
$types = [new FullyQualifiedObjectType($node->type->toString()), new NullType()];
|
||||
|
||||
return $this->typeFactory->createMixedPassedOrUnionType($types);
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasSpecificArrayType === false) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
foreach ($types as $key => $type) {
|
||||
if ($type === 'array') {
|
||||
unset($types[$key]);
|
||||
if ($node instanceof Identifier) {
|
||||
if ($node->name === 'string') {
|
||||
return new StringType();
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
if ($node instanceof FullyQualified) {
|
||||
return new FullyQualifiedObjectType($node->toString());
|
||||
}
|
||||
|
||||
if ($node instanceof Name) {
|
||||
$name = $node->toString();
|
||||
if ($this->isExistingClassLike($name)) {
|
||||
return new FullyQualifiedObjectType($node->toString());
|
||||
}
|
||||
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . 'for type ' . get_class($node));
|
||||
}
|
||||
|
||||
public function mapPHPStanPhpDocTypeToPHPStanType(PhpDocTagValueNode $phpDocTagValueNode, Node $node): Type
|
||||
{
|
||||
if ($phpDocTagValueNode instanceof ReturnTagValueNode || $phpDocTagValueNode instanceof ParamTagValueNode || $phpDocTagValueNode instanceof VarTagValueNode) {
|
||||
return $this->mapPHPStanPhpDocTypeNodeToPHPStanType($phpDocTagValueNode->type, $node);
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpDocTagValueNode));
|
||||
}
|
||||
|
||||
public function createTypeHash(Type $type): string
|
||||
{
|
||||
if ($type instanceof ArrayType) {
|
||||
// @todo sort to make different order identical
|
||||
return $this->createTypeHash($type->getItemType()) . '[]';
|
||||
}
|
||||
|
||||
if ($type instanceof ShortenedObjectType) {
|
||||
return $type->getFullyQualifiedName();
|
||||
}
|
||||
|
||||
if ($type instanceof FullyQualifiedObjectType || $type instanceof ObjectType) {
|
||||
return $type->getClassName();
|
||||
}
|
||||
|
||||
return $this->mapPHPStanTypeToDocString($type);
|
||||
}
|
||||
|
||||
public function mapStringToPHPStanType(string $newSimpleType): Type
|
||||
{
|
||||
$phpParserNode = $this->mapStringToPhpParserNode($newSimpleType);
|
||||
if ($phpParserNode === null) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return $this->mapPhpParserNodePHPStanType($phpParserNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Identifier|Name|NullableType|null
|
||||
*/
|
||||
public function mapStringToPhpParserNode(string $type): ?Node
|
||||
{
|
||||
if ($type === 'string') {
|
||||
return new Identifier('string');
|
||||
}
|
||||
|
||||
if ($type === 'array') {
|
||||
return new Identifier('array');
|
||||
}
|
||||
|
||||
if ($type === 'float') {
|
||||
return new Identifier('float');
|
||||
}
|
||||
|
||||
if (Strings::contains($type, '\\') || ctype_upper($type[0])) {
|
||||
return new FullyQualified($type);
|
||||
}
|
||||
|
||||
if (Strings::startsWith($type, '?')) {
|
||||
$nullableType = ltrim($type, '?');
|
||||
|
||||
/** @var Identifier|Name $nameNode */
|
||||
$nameNode = $this->mapStringToPhpParserNode($nullableType);
|
||||
|
||||
return new NullableType($nameNode);
|
||||
}
|
||||
|
||||
if ($type === 'void') {
|
||||
return new Identifier('void');
|
||||
}
|
||||
|
||||
throw new NotImplementedException(sprintf('%s for "%s"', __METHOD__, $type));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function mapPHPStanPhpDocTypeNodeToPHPStanType(TypeNode $typeNode, Node $node): Type
|
||||
{
|
||||
if ($typeNode instanceof IdentifierTypeNode) {
|
||||
$loweredName = strtolower($typeNode->name);
|
||||
|
||||
if ($loweredName === 'string') {
|
||||
return new StringType();
|
||||
}
|
||||
|
||||
if (in_array($loweredName, ['float', 'real', 'double'], true)) {
|
||||
return new FloatType();
|
||||
}
|
||||
|
||||
if ($loweredName === '\string') {
|
||||
return new PreSlashStringType();
|
||||
}
|
||||
|
||||
if (in_array($loweredName, ['int', 'integer'], true)) {
|
||||
return new IntegerType();
|
||||
}
|
||||
|
||||
if (in_array($loweredName, ['false', 'true', 'bool', 'boolean'], true)) {
|
||||
return new BooleanType();
|
||||
}
|
||||
|
||||
if ($loweredName === 'array') {
|
||||
return new ArrayType(new MixedType(), new MixedType());
|
||||
}
|
||||
|
||||
if ($loweredName === 'null') {
|
||||
return new NullType();
|
||||
}
|
||||
|
||||
if ($loweredName === 'void') {
|
||||
return new VoidType();
|
||||
}
|
||||
|
||||
if ($loweredName === 'object') {
|
||||
return new ObjectWithoutClassType();
|
||||
}
|
||||
|
||||
if ($loweredName === 'resource') {
|
||||
return new ResourceType();
|
||||
}
|
||||
|
||||
if (in_array($loweredName, ['callback', 'callable'], true)) {
|
||||
return new CallableType();
|
||||
}
|
||||
|
||||
if ($loweredName === 'mixed') {
|
||||
return new MixedType(true);
|
||||
}
|
||||
|
||||
if ($loweredName === 'self') {
|
||||
/** @var string|null $className */
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
if ($className === null) {
|
||||
// self outside the class, e.g. in a function
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return new SelfObjectType($className);
|
||||
}
|
||||
|
||||
if ($loweredName === 'parent') {
|
||||
/** @var string|null $parentClassName */
|
||||
$parentClassName = $node->getAttribute(AttributeKey::PARENT_CLASS_NAME);
|
||||
if ($parentClassName === null) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return new ParentStaticType($parentClassName);
|
||||
}
|
||||
|
||||
if ($loweredName === 'static') {
|
||||
/** @var string|null $className */
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
if ($className === null) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
return new StaticType($className);
|
||||
}
|
||||
|
||||
if ($loweredName === 'iterable') {
|
||||
return new IterableType(new MixedType(), new MixedType());
|
||||
}
|
||||
|
||||
// @todo improve - making many false positives now
|
||||
|
||||
$objectType = new ObjectType($typeNode->name);
|
||||
|
||||
return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAlaisedObjectType($node, $objectType);
|
||||
}
|
||||
|
||||
if ($typeNode instanceof ArrayTypeNode) {
|
||||
$nestedType = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode->type, $node);
|
||||
|
||||
return new ArrayType(new MixedType(), $nestedType);
|
||||
}
|
||||
|
||||
if ($typeNode instanceof UnionTypeNode) {
|
||||
$unionedTypes = [];
|
||||
foreach ($typeNode->types as $unionedTypeNode) {
|
||||
$unionedTypes[] = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($unionedTypeNode, $node);
|
||||
}
|
||||
|
||||
// to prevent missing class error, e.g. in tests
|
||||
return $this->typeFactory->createMixedPassedOrUnionType($unionedTypes);
|
||||
}
|
||||
|
||||
if ($typeNode instanceof ThisTypeNode) {
|
||||
if ($node === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
/** @var string $className */
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
|
||||
return new ThisType($className);
|
||||
}
|
||||
|
||||
if ($typeNode instanceof GenericTypeNode) {
|
||||
if ($typeNode->type instanceof IdentifierTypeNode) {
|
||||
if ($typeNode->type->name === 'array') {
|
||||
$genericTypes = [];
|
||||
foreach ($typeNode->genericTypes as $genericTypeNode) {
|
||||
$genericTypes[] = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($genericTypeNode, $node);
|
||||
}
|
||||
|
||||
$genericType = $this->typeFactory->createMixedPassedOrUnionType($genericTypes);
|
||||
|
||||
return new ArrayType(new MixedType(), $genericType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($typeNode));
|
||||
}
|
||||
|
||||
private function matchTypeForNullableUnionType(UnionType $unionType): ?Type
|
||||
{
|
||||
if (count($unionType->getTypes()) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$firstType = $unionType->getTypes()[0];
|
||||
$secondType = $unionType->getTypes()[1];
|
||||
|
||||
if ($firstType instanceof NullType) {
|
||||
return $secondType;
|
||||
}
|
||||
|
||||
if ($secondType instanceof NullType) {
|
||||
return $firstType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FullyQualified|null
|
||||
*/
|
||||
private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node
|
||||
{
|
||||
// we need exactly one type
|
||||
if (count($unionType->getReferencedClasses()) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($unionType->getTypes() as $unionedType) {
|
||||
if (! $unionedType instanceof TypeWithClassName) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var TypeWithClassName $firstObjectType */
|
||||
$firstObjectType = $unionType->getTypes()[0];
|
||||
|
||||
return new FullyQualified($firstObjectType->getClassName());
|
||||
}
|
||||
|
||||
private function matchArrayTypes(UnionType $unionType): ?Identifier
|
||||
{
|
||||
$isNullableType = false;
|
||||
$hasIterable = false;
|
||||
|
||||
foreach ($unionType->getTypes() as $unionedType) {
|
||||
if ($unionedType instanceof IterableType) {
|
||||
$hasIterable = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($unionedType instanceof ArrayType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($unionedType instanceof NullType) {
|
||||
$isNullableType = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($unionedType instanceof ObjectType) {
|
||||
if ($unionedType->getClassName() === Traversable::class) {
|
||||
$hasIterable = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $hasIterable ? 'iterable' : 'array';
|
||||
if ($isNullableType) {
|
||||
return new Identifier('?' . $type);
|
||||
}
|
||||
|
||||
return new Identifier($type);
|
||||
}
|
||||
|
||||
private function isExistingClassLike(string $classLike): bool
|
||||
{
|
||||
return class_exists($classLike) || interface_exists($classLike) || trait_exists($classLike);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\AbstractNodeTypeResolverTe
|
||||
use Rector\NodeTypeResolver\Tests\PerNodeTypeResolver\ParamTypeResolver\Source\Html;
|
||||
|
||||
/**
|
||||
* @covers \Rector\NodeTypeResolver\PerNodeTypeResolver\ParamTypeResolver
|
||||
* @see \Rector\NodeTypeResolver\PerNodeTypeResolver\ParamTypeResolver
|
||||
*/
|
||||
final class ParamTypeResolverTest extends AbstractNodeTypeResolverTest
|
||||
{
|
||||
|
@ -6,6 +6,8 @@ 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;
|
||||
@ -42,14 +44,14 @@ final class ReplaceTest extends AbstractKernelTestCase
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(string $originalFile, string $oldType, string $newType, string $expectedFile): void
|
||||
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->replacePhpDocTypeByAnother($phpDocInfo->getPhpDocNode(), $oldType, $newType, $node);
|
||||
$this->docBlockManipulator->renamePhpDocType($phpDocInfo->getPhpDocNode(), $oldType, $newType, $node);
|
||||
|
||||
$newPhpDocContent = $this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo);
|
||||
$this->assertStringEqualsFile($expectedFile, $newPhpDocContent);
|
||||
@ -57,11 +59,19 @@ final class ReplaceTest extends AbstractKernelTestCase
|
||||
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/ReplaceSource/before.txt', 'PHP_Filter', 'PHP\Filter', __DIR__ . '/ReplaceSource/after.txt'];
|
||||
$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',
|
||||
'PHP_Filter',
|
||||
'PHP\Filter',
|
||||
$oldObjectType,
|
||||
$newObjectType,
|
||||
__DIR__ . '/ReplaceSource/after2.txt',
|
||||
];
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\Tests;
|
||||
|
||||
use Iterator;
|
||||
use PHPStan\Type\Constant\ConstantArrayType;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\HttpKernel\RectorKernel;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
|
||||
|
||||
final class StaticTypeMapperTest extends AbstractKernelTestCase
|
||||
{
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->bootKernel(RectorKernel::class);
|
||||
|
||||
$this->staticTypeMapper = self::$container->get(StaticTypeMapper::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataForTestMapPHPStanTypeToStrings()
|
||||
* @param string[] $expectedStrings
|
||||
*/
|
||||
public function testMapPHPStanTypeToStrings(Type $type, array $expectedStrings): void
|
||||
{
|
||||
$this->assertSame($expectedStrings, $this->staticTypeMapper->mapPHPStanTypeToStrings($type));
|
||||
}
|
||||
|
||||
public function provideDataForTestMapPHPStanTypeToStrings(): Iterator
|
||||
{
|
||||
$constantArrayType = new ConstantArrayType([], [new ConstantStringType('a'), new ConstantStringType('b')]);
|
||||
|
||||
yield [$constantArrayType, ['string[]']];
|
||||
}
|
||||
}
|
9
packages/PHPStan/src/Type/AliasedObjectType.php
Normal file
9
packages/PHPStan/src/Type/AliasedObjectType.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPStan\Type;
|
||||
|
||||
use PHPStan\Type\ObjectType;
|
||||
|
||||
final class AliasedObjectType extends ObjectType
|
||||
{
|
||||
}
|
@ -2,8 +2,39 @@
|
||||
|
||||
namespace Rector\PHPStan\Type;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PhpParser\Node\Stmt\UseUse;
|
||||
use PHPStan\Type\ObjectType;
|
||||
|
||||
final class FullyQualifiedObjectType extends ObjectType
|
||||
{
|
||||
public function getShortNameType(): ShortenedObjectType
|
||||
{
|
||||
return new ShortenedObjectType($this->getShortName(), $this->getClassName());
|
||||
}
|
||||
|
||||
public function getShortName(): string
|
||||
{
|
||||
if (! Strings::contains($this->getClassName(), '\\')) {
|
||||
return $this->getClassName();
|
||||
}
|
||||
|
||||
return (string) Strings::after($this->getClassName(), '\\', -1);
|
||||
}
|
||||
|
||||
public function getUseNode(): Use_
|
||||
{
|
||||
$useUse = new UseUse(new Name($this->getClassName()));
|
||||
|
||||
return new Use_([$useUse]);
|
||||
}
|
||||
|
||||
public function getFunctionUseNode(): Use_
|
||||
{
|
||||
$useUse = new UseUse(new Name($this->getClassName()), null, Use_::TYPE_FUNCTION);
|
||||
|
||||
return new Use_([$useUse]);
|
||||
}
|
||||
}
|
||||
|
9
packages/PHPStan/src/Type/ParentStaticType.php
Normal file
9
packages/PHPStan/src/Type/ParentStaticType.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPStan\Type;
|
||||
|
||||
use PHPStan\Type\StaticType;
|
||||
|
||||
final class ParentStaticType extends StaticType
|
||||
{
|
||||
}
|
9
packages/PHPStan/src/Type/SelfObjectType.php
Normal file
9
packages/PHPStan/src/Type/SelfObjectType.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPStan\Type;
|
||||
|
||||
use PHPStan\Type\ObjectType;
|
||||
|
||||
final class SelfObjectType extends ObjectType
|
||||
{
|
||||
}
|
29
packages/PHPStan/src/Type/ShortenedObjectType.php
Normal file
29
packages/PHPStan/src/Type/ShortenedObjectType.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPStan\Type;
|
||||
|
||||
use PHPStan\Type\ObjectType;
|
||||
|
||||
final class ShortenedObjectType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $fullyQualifiedName;
|
||||
|
||||
public function __construct(string $shortName, string $fullyQualifiedName)
|
||||
{
|
||||
parent::__construct($shortName);
|
||||
$this->fullyQualifiedName = $fullyQualifiedName;
|
||||
}
|
||||
|
||||
public function getShortName(): string
|
||||
{
|
||||
return $this->getClassName();
|
||||
}
|
||||
|
||||
public function getFullyQualifiedName(): string
|
||||
{
|
||||
return $this->fullyQualifiedName;
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace Rector\PHPStan;
|
||||
|
||||
use PHPStan\Type\NullType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\UnionType;
|
||||
use ReflectionClass;
|
||||
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
|
||||
@ -11,13 +11,13 @@ use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
|
||||
final class TypeFactoryStaticHelper
|
||||
{
|
||||
/**
|
||||
* @param string[]|NullType[] $types
|
||||
* @param string[]|Type[] $types
|
||||
*/
|
||||
public static function createUnionObjectType(array $types): UnionType
|
||||
{
|
||||
$objectTypes = [];
|
||||
foreach ($types as $type) {
|
||||
if ($type instanceof NullType) {
|
||||
if ($type instanceof Type) {
|
||||
$objectTypes[] = $type;
|
||||
} else {
|
||||
$objectTypes[] = new ObjectType($type);
|
||||
|
@ -9,7 +9,6 @@ use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\PHPUnit\ValueObject\DataProviderClassMethodRecipe;
|
||||
|
||||
final class DataProviderClassMethodFactory
|
||||
@ -19,23 +18,14 @@ final class DataProviderClassMethodFactory
|
||||
*/
|
||||
private $builderFactory;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var DocBlockManipulator
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
public function __construct(
|
||||
BuilderFactory $builderFactory,
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
DocBlockManipulator $docBlockManipulator
|
||||
) {
|
||||
public function __construct(BuilderFactory $builderFactory, DocBlockManipulator $docBlockManipulator)
|
||||
{
|
||||
$this->builderFactory = $builderFactory;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
}
|
||||
|
||||
@ -67,12 +57,11 @@ final class DataProviderClassMethodFactory
|
||||
): void {
|
||||
$classMethod->returnType = new Identifier('iterable');
|
||||
|
||||
$providedType = $dataProviderClassMethodRecipe->getProvidedType();
|
||||
if ($providedType === null) {
|
||||
$type = $dataProviderClassMethodRecipe->getType();
|
||||
if ($type === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$typesAsStrings = $this->staticTypeMapper->mapPHPStanTypeToStrings($providedType);
|
||||
$this->docBlockManipulator->addReturnTag($classMethod, implode('|', $typesAsStrings));
|
||||
$this->docBlockManipulator->addReturnTag($classMethod, $type);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ use PHPStan\Type\UnionType;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
||||
use Rector\PHPUnit\NodeFactory\DataProviderClassMethodFactory;
|
||||
use Rector\PHPUnit\ValueObject\DataProviderClassMethodRecipe;
|
||||
use Rector\PHPUnit\ValueObject\ParamAndArgValueObject;
|
||||
@ -47,11 +47,6 @@ final class ArrayArgumentInTestToDataProviderRector extends AbstractPHPUnitRecto
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var DataProviderClassMethodRecipe[]
|
||||
*/
|
||||
@ -62,18 +57,23 @@ final class ArrayArgumentInTestToDataProviderRector extends AbstractPHPUnitRecto
|
||||
*/
|
||||
private $dataProviderClassMethodFactory;
|
||||
|
||||
/**
|
||||
* @var TypeFactory
|
||||
*/
|
||||
private $typeFactory;
|
||||
|
||||
/**
|
||||
* @param mixed[] $configuration
|
||||
*/
|
||||
public function __construct(
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
DataProviderClassMethodFactory $dataProviderClassMethodFactory,
|
||||
TypeFactory $typeFactory,
|
||||
array $configuration = []
|
||||
) {
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->dataProviderClassMethodFactory = $dataProviderClassMethodFactory;
|
||||
$this->typeFactory = $typeFactory;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
@ -219,7 +219,7 @@ CODE_SAMPLE
|
||||
return new PhpDocTagNode('@param', new ParamTagValueNode($typeNode, false, '$' . $name, ''));
|
||||
}
|
||||
|
||||
private function resolveUniqueArrayStaticTypes(Array_ $array): ?Type
|
||||
private function resolveUniqueArrayStaticTypes(Array_ $array): Type
|
||||
{
|
||||
$itemStaticTypes = [];
|
||||
foreach ($array->items as $arrayItem) {
|
||||
@ -228,22 +228,10 @@ CODE_SAMPLE
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueObjectHash = implode('_', $this->staticTypeMapper->mapPHPStanTypeToStrings($arrayItemStaticType));
|
||||
|
||||
$itemStaticTypes[$valueObjectHash] = new ArrayType(new MixedType(), $arrayItemStaticType);
|
||||
$itemStaticTypes[] = $arrayItemStaticType;
|
||||
}
|
||||
|
||||
$itemStaticTypes = array_values($itemStaticTypes);
|
||||
|
||||
if (count($itemStaticTypes) > 1) {
|
||||
return new UnionType($itemStaticTypes);
|
||||
}
|
||||
|
||||
if (count($itemStaticTypes) === 1) {
|
||||
return $itemStaticTypes[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->typeFactory->createMixedPassedOrUnionType($itemStaticTypes);
|
||||
}
|
||||
|
||||
private function createDataProviderMethodName(Node $node): string
|
||||
@ -337,46 +325,19 @@ CODE_SAMPLE
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $itemsStaticTypes
|
||||
* @return Type[]
|
||||
*/
|
||||
private function filterUniqueStaticTypes(array $itemsStaticTypes): array
|
||||
private function resolveItemStaticType(Array_ $array, bool $isNestedArray): Type
|
||||
{
|
||||
$uniqueStaticTypes = [];
|
||||
foreach ($itemsStaticTypes as $itemsStaticType) {
|
||||
$uniqueHash = implode('_', $this->staticTypeMapper->mapPHPStanTypeToStrings($itemsStaticType));
|
||||
$uniqueHash = md5($uniqueHash);
|
||||
|
||||
$uniqueStaticTypes[$uniqueHash] = $itemsStaticType;
|
||||
}
|
||||
|
||||
return array_values($uniqueStaticTypes);
|
||||
}
|
||||
|
||||
private function resolveItemStaticType(Array_ $array, bool $isNestedArray): ?Type
|
||||
{
|
||||
$itemsStaticTypes = [];
|
||||
$staticTypes = [];
|
||||
if ($isNestedArray === false) {
|
||||
foreach ($array->items as $arrayItem) {
|
||||
$arrayItemStaticType = $this->getStaticType($arrayItem->value);
|
||||
if ($arrayItemStaticType) {
|
||||
$itemsStaticTypes[] = $arrayItemStaticType;
|
||||
$staticTypes[] = $arrayItemStaticType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$itemsStaticTypes = $this->filterUniqueStaticTypes($itemsStaticTypes);
|
||||
|
||||
if ($itemsStaticTypes !== null && count($itemsStaticTypes) > 1) {
|
||||
return new UnionType($itemsStaticTypes);
|
||||
}
|
||||
|
||||
if (count($itemsStaticTypes) === 1) {
|
||||
return $itemsStaticTypes[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->typeFactory->createMixedPassedOrUnionType($staticTypes);
|
||||
}
|
||||
|
||||
private function isNestedArray(Array_ $array): bool
|
||||
@ -402,7 +363,7 @@ CODE_SAMPLE
|
||||
return $this->isName($methodCall->name, $singleConfiguration['old_method']);
|
||||
}
|
||||
|
||||
private function resolveUniqueArrayStaticType(Array_ $array): ?Type
|
||||
private function resolveUniqueArrayStaticType(Array_ $array): Type
|
||||
{
|
||||
$isNestedArray = $this->isNestedArray($array);
|
||||
|
||||
|
@ -20,16 +20,16 @@ final class DataProviderClassMethodRecipe
|
||||
/**
|
||||
* @var Type|null
|
||||
*/
|
||||
private $providedType;
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @param Arg[] $args
|
||||
*/
|
||||
public function __construct(string $methodName, array $args, ?Type $providedType)
|
||||
public function __construct(string $methodName, array $args, ?Type $type)
|
||||
{
|
||||
$this->methodName = $methodName;
|
||||
$this->args = $args;
|
||||
$this->providedType = $providedType;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getMethodName(): string
|
||||
@ -45,8 +45,8 @@ final class DataProviderClassMethodRecipe
|
||||
return $this->args;
|
||||
}
|
||||
|
||||
public function getProvidedType(): ?Type
|
||||
public function getType(): ?Type
|
||||
{
|
||||
return $this->providedType;
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class SomeServiceTest extends \PHPUnit\Framework\TestCase
|
||||
$this->doTestSingle($variable);
|
||||
}
|
||||
/**
|
||||
* @return int[]
|
||||
* @return int
|
||||
*/
|
||||
public function provideDataForTest(): iterable
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ class TwoArgumentsTest extends \PHPUnit\Framework\TestCase
|
||||
$this->doTestSingle($variable, $variable2);
|
||||
}
|
||||
/**
|
||||
* @return string[]
|
||||
* @return string
|
||||
*/
|
||||
public function provideDataForTest(): iterable
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRe
|
||||
class VariousTypesTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @param int|float|string $variable
|
||||
* @param float|int|string $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 float|int|string
|
||||
*/
|
||||
public function provideDataForTest(): iterable
|
||||
{
|
||||
|
@ -4,12 +4,13 @@ namespace Rector\Php\Rector\Property;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\NodeTypeResolver\ComplexNodeTypeResolver;
|
||||
use PHPStan\Type\MixedType;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
|
||||
|
||||
/**
|
||||
* @see \Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector\CompleteVarDocTypePropertyRectorTest
|
||||
@ -22,16 +23,16 @@ final class CompleteVarDocTypePropertyRector extends AbstractRector
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @var ComplexNodeTypeResolver
|
||||
* @var PropertyTypeInferer
|
||||
*/
|
||||
private $complexNodeTypeResolver;
|
||||
private $propertyTypeInferer;
|
||||
|
||||
public function __construct(
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
ComplexNodeTypeResolver $complexNodeTypeResolver
|
||||
PropertyTypeInferer $propertyTypeInferer
|
||||
) {
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->complexNodeTypeResolver = $complexNodeTypeResolver;
|
||||
$this->propertyTypeInferer = $propertyTypeInferer;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
@ -81,22 +82,19 @@ CODE_SAMPLE
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$varTypeInfo = $this->docBlockManipulator->getVarTypeInfo($node);
|
||||
if ($varTypeInfo !== null) {
|
||||
// @todo use property type resolver
|
||||
$varType = $this->docBlockManipulator->getVarType($node);
|
||||
|
||||
// already completed
|
||||
if (! $varType instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$varTypeInfo = $this->complexNodeTypeResolver->resolvePropertyTypeInfo($node);
|
||||
if ($varTypeInfo === null) {
|
||||
$varType = $this->propertyTypeInferer->inferProperty($node);
|
||||
if ($varType instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($varTypeInfo->getDocTypes() === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$varType = implode('|', $varTypeInfo->getDocTypes());
|
||||
|
||||
$this->docBlockManipulator->changeVarTag($node, $varType);
|
||||
|
||||
$node->setAttribute(AttributeKey::ORIGINAL_NODE, null);
|
||||
|
@ -3,54 +3,28 @@
|
||||
namespace Rector\Php\Rector\Property;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Scalar\DNumber;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\NodeTypeResolver\ComplexNodeTypeResolver;
|
||||
use Rector\NodeTypeResolver\Php\VarTypeInfo;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use PHPStan\Type\MixedType;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
|
||||
|
||||
/**
|
||||
* @source https://wiki.php.net/rfc/typed_properties_v2#proposal
|
||||
*
|
||||
* @see \Rector\Php\Tests\Rector\Property\TypedPropertyRector\TypedPropertyRectorTest
|
||||
*/
|
||||
final class TypedPropertyRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var string[][]
|
||||
* @var PropertyTypeInferer
|
||||
*/
|
||||
private $typeNameToAllowedDefaultNodeType = [
|
||||
'string' => [String_::class],
|
||||
'bool' => [ConstFetch::class],
|
||||
'array' => [Array_::class],
|
||||
'float' => [DNumber::class, LNumber::class],
|
||||
'int' => [LNumber::class],
|
||||
'iterable' => [Array_::class],
|
||||
];
|
||||
private $propertyTypeInferer;
|
||||
|
||||
/**
|
||||
* @var DocBlockManipulator
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @var ComplexNodeTypeResolver
|
||||
*/
|
||||
private $complexNodeTypeResolver;
|
||||
|
||||
public function __construct(
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
ComplexNodeTypeResolver $complexNodeTypeResolver
|
||||
) {
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->complexNodeTypeResolver = $complexNodeTypeResolver;
|
||||
public function __construct(PropertyTypeInferer $propertyTypeInferer)
|
||||
{
|
||||
$this->propertyTypeInferer = $propertyTypeInferer;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
@ -102,68 +76,18 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$varTypeInfos = [];
|
||||
// non FQN, so they are 1:1 to possible imported doc type
|
||||
$varTypeInfos[] = $this->docBlockManipulator->getVarTypeInfo($node);
|
||||
$varTypeInfos[] = $this->complexNodeTypeResolver->resolvePropertyTypeInfo($node);
|
||||
|
||||
$varTypeInfos = array_filter($varTypeInfos);
|
||||
|
||||
foreach ($varTypeInfos as $varTypeInfo) {
|
||||
/** @var VarTypeInfo $varTypeInfo */
|
||||
if (! $varTypeInfo->isTypehintAble()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->matchesDocTypeAndDefaultValueType($varTypeInfo, $node)) {
|
||||
$node->type = $varTypeInfo->getTypeNode();
|
||||
|
||||
return $node;
|
||||
}
|
||||
$varType = $this->propertyTypeInferer->inferProperty($node);
|
||||
if ($varType instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function matchesDocTypeAndDefaultValueType(VarTypeInfo $varTypeInfo, Property $property): bool
|
||||
{
|
||||
$defaultValueNode = $property->props[0]->default;
|
||||
if ($defaultValueNode === null) {
|
||||
return true;
|
||||
$propertyTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($varType, 'property');
|
||||
if ($propertyTypeNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! isset($this->typeNameToAllowedDefaultNodeType[$varTypeInfo->getType()])) {
|
||||
return true;
|
||||
}
|
||||
$node->type = $propertyTypeNode;
|
||||
|
||||
if ($varTypeInfo->isNullable()) {
|
||||
// is default value "null"?
|
||||
return $this->isNull($defaultValueNode);
|
||||
}
|
||||
|
||||
$allowedDefaultNodeTypes = $this->typeNameToAllowedDefaultNodeType[$varTypeInfo->getType()];
|
||||
|
||||
return $this->matchesDefaultValueToExpectedNodeTypes($varTypeInfo, $allowedDefaultNodeTypes, $defaultValueNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $allowedDefaultNodeTypes
|
||||
*/
|
||||
private function matchesDefaultValueToExpectedNodeTypes(
|
||||
VarTypeInfo $varTypeInfo,
|
||||
array $allowedDefaultNodeTypes,
|
||||
Expr $expr
|
||||
): bool {
|
||||
foreach ($allowedDefaultNodeTypes as $allowedDefaultNodeType) {
|
||||
if (is_a($expr, $allowedDefaultNodeType, true)) {
|
||||
if ($varTypeInfo->getType() === 'bool') {
|
||||
return $this->isBool($expr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,10 @@ final class CompleteVarDocTypePropertyRectorTest extends AbstractRectorTestCase
|
||||
__DIR__ . '/Fixture/property_assign.php.inc',
|
||||
__DIR__ . '/Fixture/default_value.php.inc',
|
||||
__DIR__ . '/Fixture/assign_conflict.php.inc',
|
||||
__DIR__ . '/Fixture/symfony_console_command.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',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,11 @@
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture;
|
||||
|
||||
class Robocop
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
final class AssignConflict
|
||||
{
|
||||
private $eventDispatcher;
|
||||
@ -11,7 +16,7 @@ final class AssignConflict
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public function run(\Robocop $stdClass)
|
||||
public function run(Robocop $stdClass)
|
||||
{
|
||||
$this->eventDispatcher = $stdClass;
|
||||
}
|
||||
@ -23,10 +28,15 @@ final class AssignConflict
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture;
|
||||
|
||||
class Robocop
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
final class AssignConflict
|
||||
{
|
||||
/**
|
||||
* @var \EventDispatcher|\Robocop
|
||||
* @var \EventDispatcher|\Rector\Php\Tests\Rector\Property\CompleteVarDocTypePropertyRector\Fixture\Robocop
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
@ -35,7 +45,7 @@ final class AssignConflict
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public function run(\Robocop $stdClass)
|
||||
public function run(Robocop $stdClass)
|
||||
{
|
||||
$this->eventDispatcher = $stdClass;
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ class Command
|
||||
private $application;
|
||||
private $name;
|
||||
private $processTitle;
|
||||
private $aliases = array();
|
||||
private $definition;
|
||||
private $hidden = false;
|
||||
private $help;
|
||||
@ -31,8 +30,6 @@ class Command
|
||||
private $applicationDefinitionMerged = false;
|
||||
private $applicationDefinitionMergedWithArgs = false;
|
||||
private $code;
|
||||
private $synopsis = array();
|
||||
private $usages = array();
|
||||
private $helperSet;
|
||||
/**
|
||||
* @return string|null The default command name or null when no default name is set
|
||||
@ -597,10 +594,6 @@ class Command
|
||||
* @var string
|
||||
*/
|
||||
private $processTitle;
|
||||
/**
|
||||
* @var mixed[]|string[]
|
||||
*/
|
||||
private $aliases = array();
|
||||
/**
|
||||
* @var \Symfony\Component\Console\Input\InputDefinition
|
||||
*/
|
||||
@ -633,14 +626,6 @@ class Command
|
||||
* @var callable
|
||||
*/
|
||||
private $code;
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $synopsis = array();
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $usages = array();
|
||||
/**
|
||||
* @var \Symfony\Component\Console\Helper\HelperSet|null
|
||||
*/
|
||||
|
@ -0,0 +1,6 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
final class EventDispatcher
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
|
||||
|
||||
final class BoolProperty
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
* another comment
|
||||
*/
|
||||
private $isTrue = false;
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
|
||||
|
||||
final class BoolProperty
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
* another comment
|
||||
*/
|
||||
private bool $isTrue = false;
|
||||
}
|
||||
|
||||
?>
|
@ -25,7 +25,7 @@ final class ClassWithClassProperty
|
||||
/**
|
||||
* @var AnotherClass
|
||||
*/
|
||||
private AnotherClass $anotherClass;
|
||||
private \Rector\Php\Tests\Rector\Property\TypedPropertyRector\Source\AnotherClass $anotherClass;
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -24,21 +24,6 @@ final class DefaultValues
|
||||
*/
|
||||
private $size = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $items = null;
|
||||
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private $itemsB = null;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $nullableItems = null;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
@ -96,28 +81,13 @@ final class DefaultValues
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isItRealNameNull = null;
|
||||
private ?bool $isItRealNameNull = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private bool $size = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $items = null;
|
||||
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private $itemsB = null;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private ?array $nullableItems = null;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
@ -126,7 +96,7 @@ final class DefaultValues
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private float $b = 42;
|
||||
private int $b = 42;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
@ -151,7 +121,7 @@ final class DefaultValues
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private iterable $h = [1, 2, 3];
|
||||
private array $h = [1, 2, 3];
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
|
||||
|
||||
final class DefaultValuesForNullableIterables
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $items = null;
|
||||
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private $itemsB = null;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $nullableItems = null;
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
|
||||
|
||||
final class DefaultValuesForNullableIterables
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private ?array $items = null;
|
||||
|
||||
/**
|
||||
* @var iterable
|
||||
*/
|
||||
private ?iterable $itemsB = null;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private ?array $nullableItems = null;
|
||||
}
|
||||
|
||||
?>
|
@ -48,11 +48,6 @@ final class MatchTypes
|
||||
* @var self
|
||||
*/
|
||||
private $i;
|
||||
|
||||
/**
|
||||
* @var parent
|
||||
*/
|
||||
private $j;
|
||||
}
|
||||
|
||||
?>
|
||||
@ -107,11 +102,6 @@ final class MatchTypes
|
||||
* @var self
|
||||
*/
|
||||
private self $i;
|
||||
|
||||
/**
|
||||
* @var parent
|
||||
*/
|
||||
private parent $j;
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
|
||||
|
||||
class PropperParent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
final class MatchTypesParent extends PropperParent
|
||||
{
|
||||
/**
|
||||
* @var parent
|
||||
*/
|
||||
private $j;
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
|
||||
|
||||
class PropperParent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
final class MatchTypesParent extends PropperParent
|
||||
{
|
||||
/**
|
||||
* @var parent
|
||||
*/
|
||||
private parent $j;
|
||||
}
|
||||
|
||||
?>
|
@ -9,7 +9,7 @@ final class ClassWithNullableProperty
|
||||
/**
|
||||
* @var AnotherClass|null
|
||||
*/
|
||||
private $anotherClass = null;
|
||||
private $nullableClassWithDefaultNull = null;
|
||||
|
||||
/**
|
||||
* @var null|AnotherClass
|
||||
@ -30,12 +30,12 @@ final class ClassWithNullableProperty
|
||||
/**
|
||||
* @var AnotherClass|null
|
||||
*/
|
||||
private ?AnotherClass $anotherClass = null;
|
||||
private ?\Rector\Php\Tests\Rector\Property\TypedPropertyRector\Source\AnotherClass $nullableClassWithDefaultNull = null;
|
||||
|
||||
/**
|
||||
* @var null|AnotherClass
|
||||
*/
|
||||
private ?AnotherClass $yetAnotherClass;
|
||||
private ?\Rector\Php\Tests\Rector\Property\TypedPropertyRector\Source\AnotherClass $yetAnotherClass;
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -14,12 +14,6 @@ final class ClassWithProperty
|
||||
*/
|
||||
private $multiCount;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* another comment
|
||||
*/
|
||||
private $isTrue = false;
|
||||
|
||||
/**
|
||||
* @var void
|
||||
*/
|
||||
@ -29,11 +23,6 @@ final class ClassWithProperty
|
||||
* @var callable
|
||||
*/
|
||||
private $shouldBeSkippedToo;
|
||||
|
||||
/**
|
||||
* @var invalid
|
||||
*/
|
||||
private $cantTouchThis;
|
||||
}
|
||||
|
||||
?>
|
||||
@ -54,12 +43,6 @@ final class ClassWithProperty
|
||||
*/
|
||||
private $multiCount;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* another comment
|
||||
*/
|
||||
private bool $isTrue = false;
|
||||
|
||||
/**
|
||||
* @var void
|
||||
*/
|
||||
@ -69,11 +52,6 @@ final class ClassWithProperty
|
||||
* @var callable
|
||||
*/
|
||||
private $shouldBeSkippedToo;
|
||||
|
||||
/**
|
||||
* @var invalid
|
||||
*/
|
||||
private $cantTouchThis;
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\Property\TypedPropertyRector\Fixture;
|
||||
|
||||
final class SkipInvalidProperty
|
||||
{
|
||||
/**
|
||||
* @var invalid
|
||||
*/
|
||||
private $cantTouchThis;
|
||||
}
|
@ -11,11 +11,15 @@ final class TypedPropertyRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/property.php.inc',
|
||||
__DIR__ . '/Fixture/skip_invalid_property.php.inc',
|
||||
__DIR__ . '/Fixture/bool_property.php.inc',
|
||||
__DIR__ . '/Fixture/class_property.php.inc',
|
||||
__DIR__ . '/Fixture/nullable_property.php.inc',
|
||||
__DIR__ . '/Fixture/static_property.php.inc',
|
||||
__DIR__ . '/Fixture/default_values_for_nullable_iterables.php.inc',
|
||||
__DIR__ . '/Fixture/default_values.php.inc',
|
||||
__DIR__ . '/Fixture/match_types.php.inc',
|
||||
__DIR__ . '/Fixture/match_types_parent.php.inc',
|
||||
__DIR__ . '/Fixture/static_analysis_based.php.inc',
|
||||
]);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\UnionType;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\PhpParser\Node\VariableInfo;
|
||||
use Rector\PhpSpecToPHPUnit\PhpSpecMockCollector;
|
||||
use Rector\PhpSpecToPHPUnit\Rector\AbstractPhpSpecToPHPUnitRector;
|
||||
|
||||
@ -52,7 +51,7 @@ final class AddMockPropertiesRector extends AbstractPhpSpecToPHPUnitRector
|
||||
/** @var string $class */
|
||||
$class = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
|
||||
foreach ($classMocks as $variable => $methods) {
|
||||
foreach ($classMocks as $name => $methods) {
|
||||
if (count($methods) <= 1) {
|
||||
continue;
|
||||
}
|
||||
@ -62,17 +61,16 @@ final class AddMockPropertiesRector extends AbstractPhpSpecToPHPUnitRector
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->phpSpecMockCollector->addPropertyMock($class, $variable);
|
||||
$this->phpSpecMockCollector->addPropertyMock($class, $name);
|
||||
|
||||
$variableType = $this->phpSpecMockCollector->getTypeForClassAndVariable($node, $variable);
|
||||
$variableType = $this->phpSpecMockCollector->getTypeForClassAndVariable($node, $name);
|
||||
|
||||
$unionType = new UnionType([
|
||||
new ObjectType($variableType),
|
||||
new ObjectType('PHPUnit\Framework\MockObject\MockObject'),
|
||||
]);
|
||||
|
||||
$variableInfo = new VariableInfo($variable, $unionType);
|
||||
$this->classManipulator->addPropertyToClass($node, $variableInfo);
|
||||
$this->classManipulator->addPropertyToClass($node, $name, $unionType);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -15,9 +15,7 @@ use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\PhpParser\Node\VariableInfo;
|
||||
use Rector\PhpSpecToPHPUnit\LetManipulator;
|
||||
use Rector\PhpSpecToPHPUnit\Naming\PhpSpecRenaming;
|
||||
use Rector\PhpSpecToPHPUnit\PHPUnitTypeDeclarationDecorator;
|
||||
@ -50,23 +48,16 @@ final class PhpSpecClassToPHPUnitClassRector extends AbstractPhpSpecToPHPUnitRec
|
||||
*/
|
||||
private $letManipulator;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
public function __construct(
|
||||
ClassManipulator $classManipulator,
|
||||
PhpSpecRenaming $phpSpecRenaming,
|
||||
PHPUnitTypeDeclarationDecorator $phpUnitTypeDeclarationDecorator,
|
||||
LetManipulator $letManipulator,
|
||||
StaticTypeMapper $staticTypeMapper
|
||||
LetManipulator $letManipulator
|
||||
) {
|
||||
$this->classManipulator = $classManipulator;
|
||||
$this->phpSpecRenaming = $phpSpecRenaming;
|
||||
$this->phpUnitTypeDeclarationDecorator = $phpUnitTypeDeclarationDecorator;
|
||||
$this->letManipulator = $letManipulator;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,9 +86,9 @@ final class PhpSpecClassToPHPUnitClassRector extends AbstractPhpSpecToPHPUnitRec
|
||||
$this->phpSpecRenaming->renameExtends($node);
|
||||
|
||||
$testedClass = $this->phpSpecRenaming->resolveTestedClass($node);
|
||||
$this->testedObjectType = new ObjectType($testedClass);
|
||||
|
||||
$this->classManipulator->addPropertyToClass($node, new VariableInfo($propertyName, $this->testedObjectType));
|
||||
$this->testedObjectType = new ObjectType($testedClass);
|
||||
$this->classManipulator->addPropertyToClass($node, $propertyName, $this->testedObjectType);
|
||||
|
||||
// add let if missing
|
||||
if ($node->getMethod('let') === null) {
|
||||
|
@ -23,9 +23,7 @@ use PHPStan\Type\ObjectType;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\Naming\PropertyNaming;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
|
||||
use Rector\PhpParser\Node\VariableInfo;
|
||||
use Rector\PhpSpecToPHPUnit\PHPUnitTypeDeclarationDecorator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\ConfiguredCodeSample;
|
||||
@ -68,11 +66,6 @@ final class PHPUnitStaticToKernelTestCaseGetRector extends AbstractRector
|
||||
*/
|
||||
private $phpUnitTypeDeclarationDecorator;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @param string[] $staticClassTypes
|
||||
*/
|
||||
@ -80,7 +73,6 @@ final class PHPUnitStaticToKernelTestCaseGetRector extends AbstractRector
|
||||
PropertyNaming $propertyNaming,
|
||||
ClassManipulator $classManipulator,
|
||||
PHPUnitTypeDeclarationDecorator $phpUnitTypeDeclarationDecorator,
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
array $staticClassTypes = [],
|
||||
string $kernelTestCaseClass = SymfonyClass::KERNEL_TEST_CASE
|
||||
) {
|
||||
@ -89,7 +81,6 @@ final class PHPUnitStaticToKernelTestCaseGetRector extends AbstractRector
|
||||
$this->kernelTestCaseClass = $kernelTestCaseClass;
|
||||
$this->classManipulator = $classManipulator;
|
||||
$this->phpUnitTypeDeclarationDecorator = $phpUnitTypeDeclarationDecorator;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
@ -239,7 +230,7 @@ CODE_SAMPLE
|
||||
{
|
||||
$propertyName = $this->propertyNaming->fqnToVariableName($objectType);
|
||||
|
||||
return $this->nodeFactory->createPrivatePropertyFromVariableInfo(new VariableInfo($propertyName, $objectType));
|
||||
return $this->nodeFactory->createPrivatePropertyFromNameAndType($propertyName, $objectType);
|
||||
}
|
||||
|
||||
private function convertStaticCallToPropertyMethodCall(StaticCall $staticCall, ObjectType $objectType): MethodCall
|
||||
|
@ -176,7 +176,7 @@ CODE_SAMPLE
|
||||
$entityFactoryPropertyBuilder->makePrivate();
|
||||
$entityFactoryProperty = $entityFactoryPropertyBuilder->getNode();
|
||||
|
||||
$this->docBlockManipulator->changeVarTag($entityFactoryProperty, $staticType);
|
||||
$this->docBlockManipulator->changeVarTag($entityFactoryProperty, $objectType);
|
||||
|
||||
$class->stmts = array_merge([$entityFactoryProperty, $setEntityFactoryMethod], $class->stmts);
|
||||
|
||||
|
@ -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\MixedType;
|
||||
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
@ -299,7 +300,7 @@ CODE_SAMPLE
|
||||
$classMethod->returnType = new Identifier('array');
|
||||
}
|
||||
|
||||
$this->docBlockManipulator->addReturnTag($classMethod, 'mixed[]');
|
||||
$this->docBlockManipulator->addReturnTag($classMethod, new MixedType(true));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,6 @@ use PhpParser\Node\Stmt\Property;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use Rector\Naming\PropertyNaming;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpParser\Node\VariableInfo;
|
||||
use Rector\PhpSpecToPHPUnit\PHPUnitTypeDeclarationDecorator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
@ -281,9 +280,8 @@ CODE_SAMPLE
|
||||
continue;
|
||||
}
|
||||
|
||||
$variableInfo = new VariableInfo($propertyName, new ObjectType($serviceType));
|
||||
|
||||
$properties[] = $this->nodeFactory->createPrivatePropertyFromVariableInfo($variableInfo);
|
||||
$serviceType = new ObjectType($serviceType);
|
||||
$properties[] = $this->nodeFactory->createPrivatePropertyFromNameAndType($propertyName, $serviceType);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
|
@ -3,11 +3,9 @@
|
||||
namespace Rector\TypeDeclaration\Contract\TypeInferer;
|
||||
|
||||
use PhpParser\Node\Param;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
interface ParamTypeInfererInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function inferParam(Param $param): array;
|
||||
public function inferParam(Param $param): Type;
|
||||
}
|
||||
|
@ -3,12 +3,9 @@
|
||||
namespace Rector\TypeDeclaration\Contract\TypeInferer;
|
||||
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use Rector\TypeDeclaration\ValueObject\IdentifierValueObject;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
interface PropertyTypeInfererInterface extends PriorityAwareTypeInfererInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]|IdentifierValueObject[]
|
||||
*/
|
||||
public function inferProperty(Property $property): array;
|
||||
public function inferProperty(Property $property): Type;
|
||||
}
|
||||
|
@ -3,11 +3,9 @@
|
||||
namespace Rector\TypeDeclaration\Contract\TypeInferer;
|
||||
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
interface ReturnTypeInfererInterface extends PriorityAwareTypeInfererInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function inferFunctionLike(FunctionLike $functionLike): array;
|
||||
public function inferFunctionLike(FunctionLike $functionLike): Type;
|
||||
}
|
||||
|
@ -0,0 +1,125 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\PHPStan\Type;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PHPStan\Type\AliasedObjectType;
|
||||
use Rector\PHPStan\Type\FullyQualifiedObjectType;
|
||||
use Rector\PHPStan\Type\ShortenedObjectType;
|
||||
|
||||
final class ObjectTypeSpecifier
|
||||
{
|
||||
/**
|
||||
* @return AliasedObjectType|FullyQualifiedObjectType|ObjectType|MixedType
|
||||
*/
|
||||
public function narrowToFullyQualifiedOrAlaisedObjectType(Node $node, ObjectType $objectType): Type
|
||||
{
|
||||
/** @var Node\Stmt\Use_[]|null $uses */
|
||||
$uses = $node->getAttribute(AttributeKey::USE_NODES);
|
||||
if ($uses === null) {
|
||||
return $objectType;
|
||||
}
|
||||
|
||||
$aliasedObjectType = $this->matchAliasedObjectType($node, $objectType);
|
||||
if ($aliasedObjectType) {
|
||||
return $aliasedObjectType;
|
||||
}
|
||||
|
||||
$shortednedObjectType = $this->matchShortenedObjectType($node, $objectType);
|
||||
if ($shortednedObjectType) {
|
||||
return $shortednedObjectType;
|
||||
}
|
||||
|
||||
$sameNamespacedObjectType = $this->matchSameNamespacedObjectType($node, $objectType);
|
||||
if ($sameNamespacedObjectType) {
|
||||
return $sameNamespacedObjectType;
|
||||
}
|
||||
|
||||
$className = ltrim($objectType->getClassName(), '\\');
|
||||
|
||||
if ($this->isExistingClassLike($className)) {
|
||||
return new FullyQualifiedObjectType($className);
|
||||
}
|
||||
|
||||
// invalid type
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
private function isExistingClassLike(string $classLike): bool
|
||||
{
|
||||
return class_exists($classLike) || interface_exists($classLike) || trait_exists($classLike);
|
||||
}
|
||||
|
||||
private function matchAliasedObjectType(Node $node, ObjectType $objectType): ?AliasedObjectType
|
||||
{
|
||||
/** @var Node\Stmt\Use_[]|null $uses */
|
||||
$uses = $node->getAttribute(AttributeKey::USE_NODES);
|
||||
if ($uses === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($uses as $use) {
|
||||
foreach ($use->uses as $useUse) {
|
||||
if ($useUse->alias === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$useName = $useUse->name->toString();
|
||||
if ($useName !== $objectType->getClassName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new AliasedObjectType($useUse->alias->toString());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function matchShortenedObjectType(Node $node, ObjectType $objectType): ?ShortenedObjectType
|
||||
{
|
||||
/** @var Node\Stmt\Use_[]|null $uses */
|
||||
$uses = $node->getAttribute(AttributeKey::USE_NODES);
|
||||
if ($uses === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($uses as $use) {
|
||||
foreach ($use->uses as $useUse) {
|
||||
if ($useUse->alias) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($useUse->name->getLast() !== $objectType->getClassName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isExistingClassLike($useUse->name->toString())) {
|
||||
return new ShortenedObjectType($objectType->getClassName(), $useUse->name->toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function matchSameNamespacedObjectType(Node $node, ObjectType $objectType): ?ObjectType
|
||||
{
|
||||
$namespaceName = $node->getAttribute(AttributeKey::NAMESPACE_NAME);
|
||||
if ($namespaceName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$namespacedObject = $namespaceName . '\\' . $objectType->getClassName();
|
||||
|
||||
if ($this->isExistingClassLike($namespacedObject)) {
|
||||
return new FullyQualifiedObjectType($namespacedObject);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -3,12 +3,10 @@
|
||||
namespace Rector\TypeDeclaration\PhpDocParser;
|
||||
|
||||
use PhpParser\Node\Param;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareParamTagValueNode;
|
||||
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocTagNode;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
|
||||
final class ParamPhpDocNodeFactory
|
||||
@ -18,33 +16,23 @@ final class ParamPhpDocNodeFactory
|
||||
*/
|
||||
private $nameResolver;
|
||||
|
||||
public function __construct(NameResolver $nameResolver)
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
public function __construct(NameResolver $nameResolver, StaticTypeMapper $staticTypeMapper)
|
||||
{
|
||||
$this->nameResolver = $nameResolver;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
*/
|
||||
public function create(array $types, Param $param): AttributeAwarePhpDocTagNode
|
||||
public function create(Type $type, Param $param): AttributeAwarePhpDocTagNode
|
||||
{
|
||||
if (count($types) > 1) {
|
||||
$unionedTypes = [];
|
||||
foreach ($types as $type) {
|
||||
$unionedTypes[] = $this->createIdentifierTypeNode($type);
|
||||
}
|
||||
|
||||
$typeNode = new UnionTypeNode($unionedTypes);
|
||||
} elseif (count($types) === 1) {
|
||||
$typeNode = $this->createIdentifierTypeNode($types[0]);
|
||||
} else {
|
||||
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
|
||||
}
|
||||
|
||||
$arrayTypeNode = new ArrayTypeNode($typeNode);
|
||||
$typeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($type);
|
||||
|
||||
$paramTagValueNode = new AttributeAwareParamTagValueNode(
|
||||
$arrayTypeNode,
|
||||
$typeNode,
|
||||
$param->variadic,
|
||||
'$' . $this->nameResolver->getName($param),
|
||||
'',
|
||||
@ -53,14 +41,4 @@ final class ParamPhpDocNodeFactory
|
||||
|
||||
return new AttributeAwarePhpDocTagNode('@param', $paramTagValueNode);
|
||||
}
|
||||
|
||||
private function createIdentifierTypeNode(string $type): IdentifierTypeNode
|
||||
{
|
||||
if (class_exists($type)) {
|
||||
// FQN class name
|
||||
$type = '\\' . $type;
|
||||
}
|
||||
|
||||
return new IdentifierTypeNode($type);
|
||||
}
|
||||
}
|
||||
|
@ -108,12 +108,12 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$types = $this->paramTypeInferer->inferParam($param);
|
||||
if ($types === []) {
|
||||
$type = $this->paramTypeInferer->inferParam($param);
|
||||
if ($type instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$paramTagNode = $this->paramPhpDocNodeFactory->create($types, $param);
|
||||
$paramTagNode = $this->paramPhpDocNodeFactory->create($type, $param);
|
||||
$this->docBlockManipulator->addTag($node, $paramTagNode);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnTypeDeclarationRe
|
||||
|
||||
/**
|
||||
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
|
||||
*
|
||||
* @see \Rector\TypeDeclaration\Tests\Rector\ClassMethod\AddArrayReturnDocTypeRector\AddArrayReturnDocTypeRectorTest
|
||||
*/
|
||||
final class AddArrayReturnDocTypeRector extends AbstractRector
|
||||
@ -90,21 +91,12 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$inferedTypes = $this->returnTypeInferer->inferFunctionLikeWithExcludedInferers(
|
||||
$inferedType = $this->returnTypeInferer->inferFunctionLikeWithExcludedInferers(
|
||||
$node,
|
||||
[ReturnTypeDeclarationReturnTypeInferer::class]
|
||||
);
|
||||
if ($inferedTypes === ['void']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($inferedTypes === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$docType = implode('|', $inferedTypes);
|
||||
|
||||
$this->docBlockManipulator->addReturnTag($node, $docType);
|
||||
$this->docBlockManipulator->addReturnTag($node, $inferedType);
|
||||
|
||||
return $node;
|
||||
}
|
||||
@ -115,7 +107,7 @@ CODE_SAMPLE
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($classMethod->returnType) {
|
||||
if ($classMethod->returnType !== null) {
|
||||
if (! $this->isNames($classMethod->returnType, ['array', 'iterable'])) {
|
||||
return true;
|
||||
}
|
||||
|
@ -6,8 +6,6 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
@ -23,15 +21,9 @@ final class AddClosureReturnTypeRector extends AbstractRector
|
||||
*/
|
||||
private $returnTypeInferer;
|
||||
|
||||
/**
|
||||
* @var TypeAnalyzer
|
||||
*/
|
||||
private $typeAnalyzer;
|
||||
|
||||
public function __construct(ReturnTypeInferer $returnTypeInferer, TypeAnalyzer $typeAnalyzer)
|
||||
public function __construct(ReturnTypeInferer $returnTypeInferer)
|
||||
{
|
||||
$this->returnTypeInferer = $returnTypeInferer;
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
@ -92,10 +84,9 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$inferedReturnTypes = $this->returnTypeInferer->inferFunctionLike($node);
|
||||
$inferedReturnType = $this->returnTypeInferer->inferFunctionLike($node);
|
||||
|
||||
$returnTypeInfo = new ReturnTypeInfo($inferedReturnTypes, $this->typeAnalyzer, $inferedReturnTypes);
|
||||
$returnTypeNode = $returnTypeInfo->getFqnTypeNode();
|
||||
$returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($inferedReturnType);
|
||||
if ($returnTypeNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ namespace Rector\TypeDeclaration\Rector\FunctionLike;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
@ -13,11 +12,14 @@ use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PhpParser\Node\Stmt\Interface_;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\StaticType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeContainer\ParsedNodesByType;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\Php\AbstractTypeInfo;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\PHPStan\Type\SelfObjectType;
|
||||
use Rector\Rector\AbstractRector;
|
||||
|
||||
/**
|
||||
@ -159,41 +161,21 @@ abstract class AbstractTypeDeclarationRector extends AbstractRector
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod|Param $node
|
||||
* @return Name|NullableType|Identifier|null
|
||||
*/
|
||||
protected function resolveChildType(AbstractTypeInfo $returnTypeInfo, Node $node): ?Node
|
||||
protected function resolveChildTypeNode(Type $type): ?Node
|
||||
{
|
||||
$nakedType = $returnTypeInfo->getTypeNode() instanceof NullableType ? $returnTypeInfo->getTypeNode()->type : $returnTypeInfo->getTypeNode();
|
||||
|
||||
if ($nakedType === null) {
|
||||
if ($type instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($nakedType->toString() === 'self') {
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
if ($className === null) {
|
||||
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
|
||||
}
|
||||
|
||||
$type = new FullyQualified($className);
|
||||
|
||||
return $returnTypeInfo->isNullable() ? new NullableType($type) : $type;
|
||||
if ($type instanceof SelfObjectType) {
|
||||
$type = new ObjectType($type->getClassName());
|
||||
} elseif ($type instanceof StaticType) {
|
||||
$type = new ObjectType($type->getClassName());
|
||||
}
|
||||
|
||||
if ($nakedType->toString() === 'parent') {
|
||||
$parentClassName = $node->getAttribute(AttributeKey::PARENT_CLASS_NAME);
|
||||
if ($parentClassName === null) {
|
||||
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
|
||||
}
|
||||
|
||||
$type = new FullyQualified($parentClassName);
|
||||
|
||||
return $returnTypeInfo->isNullable() ? new NullableType($type) : $type;
|
||||
}
|
||||
|
||||
// are namespaces different? → FQN name
|
||||
return $returnTypeInfo->getFqnTypeNode();
|
||||
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type);
|
||||
}
|
||||
|
||||
private function hasParentClassOrImplementsInterface(ClassMethod $classMethod): bool
|
||||
|
@ -9,8 +9,8 @@ use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\Php\ParamTypeInfo;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
@ -101,10 +101,10 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$paramTagInfos = $this->docBlockManipulator->getParamTypeInfos($node);
|
||||
$paramWithTypes = $this->docBlockManipulator->getParamTypesByName($node);
|
||||
|
||||
// no tags, nothing to complete here
|
||||
if ($paramTagInfos === []) {
|
||||
if ($paramWithTypes === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -123,16 +123,16 @@ CODE_SAMPLE
|
||||
}
|
||||
}
|
||||
|
||||
$paramNodeName = $this->getName($paramNode->var);
|
||||
$paramNodeName = '$' . $this->getName($paramNode->var);
|
||||
|
||||
// no info about it
|
||||
if (! isset($paramTagInfos[$paramNodeName])) {
|
||||
if (! isset($paramWithTypes[$paramNodeName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paramTypeInfo = $paramTagInfos[$paramNodeName];
|
||||
|
||||
if (! $paramTypeInfo->isTypehintAble()) {
|
||||
$paramType = $paramWithTypes[$paramNodeName];
|
||||
$paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($paramType, 'param');
|
||||
if ($paramTypeNode === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -143,19 +143,17 @@ CODE_SAMPLE
|
||||
|
||||
if ($hasNewType) {
|
||||
// should override - is it subtype?
|
||||
$possibleOverrideNewReturnType = $paramTypeInfo->getFqnTypeNode();
|
||||
$possibleOverrideNewReturnType = $paramTypeNode;
|
||||
if ($possibleOverrideNewReturnType !== null) {
|
||||
if ($paramNode->type !== null) {
|
||||
if ($this->isSubtypeOf($possibleOverrideNewReturnType, $paramNode->type)) {
|
||||
// allow override
|
||||
$paramNode->type = $paramTypeInfo->getFqnTypeNode();
|
||||
}
|
||||
} else {
|
||||
$paramNode->type = $paramTypeInfo->getTypeNode();
|
||||
if ($paramNode->type === null) {
|
||||
$paramNode->type = $paramTypeNode;
|
||||
} elseif ($this->isSubtypeOf($possibleOverrideNewReturnType, $paramNode->type)) {
|
||||
// allow override
|
||||
$paramNode->type = $paramTypeNode;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$paramNode->type = $paramTypeInfo->getFqnTypeNode();
|
||||
$paramNode->type = $paramTypeNode;
|
||||
|
||||
$paramNodeType = $paramNode->type instanceof NullableType ? $paramNode->type->type : $paramNode->type;
|
||||
// "resource" is valid phpdoc type, but it's not implemented in PHP
|
||||
@ -166,7 +164,7 @@ CODE_SAMPLE
|
||||
}
|
||||
}
|
||||
|
||||
$this->populateChildren($node, $position, $paramTypeInfo);
|
||||
$this->populateChildren($node, $position, $paramType);
|
||||
}
|
||||
|
||||
return $node;
|
||||
@ -176,7 +174,7 @@ CODE_SAMPLE
|
||||
* Add typehint to all children
|
||||
* @param ClassMethod|Function_ $node
|
||||
*/
|
||||
private function populateChildren(Node $node, int $position, ParamTypeInfo $paramTypeInfo): void
|
||||
private function populateChildren(Node $node, int $position, Type $paramType): void
|
||||
{
|
||||
if (! $node instanceof ClassMethod) {
|
||||
return;
|
||||
@ -197,11 +195,11 @@ CODE_SAMPLE
|
||||
$usedTraits = $this->parsedNodesByType->findUsedTraitsInClass($childClassLike);
|
||||
|
||||
foreach ($usedTraits as $trait) {
|
||||
$this->addParamTypeToMethod($trait, $position, $node, $paramTypeInfo);
|
||||
$this->addParamTypeToMethod($trait, $position, $node, $paramType);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addParamTypeToMethod($childClassLike, $position, $node, $paramTypeInfo);
|
||||
$this->addParamTypeToMethod($childClassLike, $position, $node, $paramType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +207,7 @@ CODE_SAMPLE
|
||||
ClassLike $classLike,
|
||||
int $position,
|
||||
ClassMethod $classMethod,
|
||||
ParamTypeInfo $paramTypeInfo
|
||||
Type $paramType
|
||||
): void {
|
||||
$methodName = $this->getName($classMethod);
|
||||
|
||||
@ -229,14 +227,13 @@ CODE_SAMPLE
|
||||
return;
|
||||
}
|
||||
|
||||
$resolvedChildType = $this->resolveChildType($paramTypeInfo, $classMethod);
|
||||
$resolvedChildType = $this->resolveChildTypeNode($paramType);
|
||||
if ($resolvedChildType === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$paramNode->type = $resolvedChildType;
|
||||
|
||||
// let the method know it was changed now
|
||||
$paramNode->type = $resolvedChildType;
|
||||
$paramNode->type->setAttribute(self::HAS_NEW_INHERITED_TYPE, true);
|
||||
|
||||
$this->notifyNodeChangeFileInfo($paramNode);
|
||||
|
@ -6,10 +6,10 @@ use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
|
||||
@ -27,28 +27,24 @@ final class ReturnTypeDeclarationRector extends AbstractTypeDeclarationRector
|
||||
*/
|
||||
private const EXCLUDED_METHOD_NAMES = ['__construct', '__destruct', '__clone'];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const DO_NOT_CHANGE = 'do_not_change';
|
||||
|
||||
/**
|
||||
* @var ReturnTypeInferer
|
||||
*/
|
||||
private $returnTypeInferer;
|
||||
|
||||
/**
|
||||
* @var TypeAnalyzer
|
||||
*/
|
||||
private $typeAnalyzer;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $overrideExistingReturnTypes = true;
|
||||
|
||||
public function __construct(
|
||||
ReturnTypeInferer $returnTypeInferer,
|
||||
TypeAnalyzer $typeAnalyzer,
|
||||
bool $overrideExistingReturnTypes = true
|
||||
) {
|
||||
public function __construct(ReturnTypeInferer $returnTypeInferer, bool $overrideExistingReturnTypes = true)
|
||||
{
|
||||
$this->returnTypeInferer = $returnTypeInferer;
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
$this->overrideExistingReturnTypes = $overrideExistingReturnTypes;
|
||||
}
|
||||
|
||||
@ -103,40 +99,45 @@ CODE_SAMPLE
|
||||
return null;
|
||||
}
|
||||
|
||||
$inferedTypes = $this->returnTypeInferer->inferFunctionLikeWithExcludedInferers(
|
||||
$inferedType = $this->returnTypeInferer->inferFunctionLikeWithExcludedInferers(
|
||||
$node,
|
||||
[ReturnTypeDeclarationReturnTypeInferer::class]
|
||||
);
|
||||
|
||||
if ($inferedTypes === []) {
|
||||
if ($inferedType instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnTypeInfo = new ReturnTypeInfo($inferedTypes, $this->typeAnalyzer, $inferedTypes);
|
||||
if ($this->isReturnTypeAlreadyAdded($node, $returnTypeInfo)) {
|
||||
if ($this->isReturnTypeAlreadyAdded($node, $inferedType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$inferredReturnNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($inferedType);
|
||||
|
||||
// already overridden by previous populateChild() method run
|
||||
if ($node->returnType && $node->returnType->getAttribute(self::DO_NOT_CHANGE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$shouldPopulateChildren = false;
|
||||
// should be previous overridden?
|
||||
if ($node->returnType !== null && $returnTypeInfo->getFqnTypeNode() !== null) {
|
||||
$isSubtype = $this->isSubtypeOf($returnTypeInfo->getFqnTypeNode(), $node->returnType);
|
||||
if ($node->returnType !== null && $inferredReturnNode !== null) {
|
||||
$isSubtype = $this->isSubtypeOf($inferredReturnNode, $node->returnType);
|
||||
|
||||
// @see https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters
|
||||
if ($this->isAtLeastPhpVersion('7.4') && $isSubtype) {
|
||||
$shouldPopulateChildren = true;
|
||||
$node->returnType = $returnTypeInfo->getFqnTypeNode();
|
||||
$node->returnType = $inferredReturnNode;
|
||||
} elseif ($isSubtype === false) { // type override
|
||||
$shouldPopulateChildren = true;
|
||||
$node->returnType = $returnTypeInfo->getFqnTypeNode();
|
||||
$node->returnType = $inferredReturnNode;
|
||||
}
|
||||
} elseif ($returnTypeInfo->getFqnTypeNode() !== null) {
|
||||
} elseif ($inferredReturnNode !== null) {
|
||||
$shouldPopulateChildren = true;
|
||||
$node->returnType = $returnTypeInfo->getFqnTypeNode();
|
||||
$node->returnType = $inferredReturnNode;
|
||||
}
|
||||
|
||||
if ($shouldPopulateChildren) {
|
||||
$this->populateChildren($node, $returnTypeInfo);
|
||||
if ($shouldPopulateChildren && $node instanceof ClassMethod) {
|
||||
$this->populateChildren($node, $inferedType);
|
||||
}
|
||||
|
||||
return $node;
|
||||
@ -145,18 +146,14 @@ CODE_SAMPLE
|
||||
/**
|
||||
* Add typehint to all children class methods
|
||||
*/
|
||||
private function populateChildren(Node $node, ReturnTypeInfo $returnTypeInfo): void
|
||||
private function populateChildren(ClassMethod $classMethod, Type $returnType): void
|
||||
{
|
||||
if (! $node instanceof ClassMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
$methodName = $this->getName($node);
|
||||
$methodName = $this->getName($classMethod);
|
||||
if ($methodName === null) {
|
||||
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
|
||||
}
|
||||
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
|
||||
if (! is_string($className)) {
|
||||
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
|
||||
}
|
||||
@ -167,17 +164,17 @@ CODE_SAMPLE
|
||||
foreach ($childrenClassLikes as $childClassLike) {
|
||||
$usedTraits = $this->parsedNodesByType->findUsedTraitsInClass($childClassLike);
|
||||
foreach ($usedTraits as $trait) {
|
||||
$this->addReturnTypeToMethod($trait, $node, $returnTypeInfo);
|
||||
$this->addReturnTypeToChildMethod($trait, $classMethod, $returnType);
|
||||
}
|
||||
|
||||
$this->addReturnTypeToMethod($childClassLike, $node, $returnTypeInfo);
|
||||
$this->addReturnTypeToChildMethod($childClassLike, $classMethod, $returnType);
|
||||
}
|
||||
}
|
||||
|
||||
private function addReturnTypeToMethod(
|
||||
private function addReturnTypeToChildMethod(
|
||||
ClassLike $classLike,
|
||||
ClassMethod $classMethod,
|
||||
ReturnTypeInfo $returnTypeInfo
|
||||
Type $returnType
|
||||
): void {
|
||||
$methodName = $this->getName($classMethod);
|
||||
|
||||
@ -191,12 +188,15 @@ CODE_SAMPLE
|
||||
return;
|
||||
}
|
||||
|
||||
$resolvedChildType = $this->resolveChildType($returnTypeInfo, $classMethod);
|
||||
if ($resolvedChildType === null) {
|
||||
$resolvedChildTypeNode = $this->resolveChildTypeNode($returnType);
|
||||
if ($resolvedChildTypeNode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentClassMethod->returnType = $resolvedChildType;
|
||||
$currentClassMethod->returnType = $resolvedChildTypeNode;
|
||||
|
||||
// make sure the type is not overridden
|
||||
$currentClassMethod->returnType->setAttribute(self::DO_NOT_CHANGE, true);
|
||||
|
||||
$this->notifyNodeChangeFileInfo($currentClassMethod);
|
||||
}
|
||||
@ -222,17 +222,22 @@ CODE_SAMPLE
|
||||
/**
|
||||
* @param ClassMethod|Function_ $node
|
||||
*/
|
||||
private function isReturnTypeAlreadyAdded(Node $node, ReturnTypeInfo $returnTypeInfo): bool
|
||||
private function isReturnTypeAlreadyAdded(Node $node, Type $returnType): bool
|
||||
{
|
||||
if (ltrim($this->print($node->returnType), '\\') === $this->print($returnTypeInfo->getTypeNode())) {
|
||||
$returnNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType);
|
||||
|
||||
if ($node->returnType === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->print($node->returnType) === $this->print($returnNode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// prevent overriding self with itself
|
||||
if ($this->print($node->returnType) === 'self') {
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
|
||||
if (ltrim($this->print($returnTypeInfo->getFqnTypeNode()), '\\') === $className) {
|
||||
if (ltrim($this->print($returnNode), '\\') === $className) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ namespace Rector\TypeDeclaration\Rector\Property;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PHPStan\Type\MixedType;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
@ -13,6 +14,7 @@ use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
|
||||
|
||||
/**
|
||||
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
|
||||
*
|
||||
* @see \Rector\TypeDeclaration\Tests\Rector\Property\PropertyTypeDeclarationRector\PropertyTypeDeclarationRectorTest
|
||||
*/
|
||||
final class PropertyTypeDeclarationRector extends AbstractRector
|
||||
@ -35,7 +37,7 @@ final class PropertyTypeDeclarationRector extends AbstractRector
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Add missing @var to properties that are missing it');
|
||||
return new RectorDefinition('Add @var to properties that are missing it');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,12 +61,12 @@ final class PropertyTypeDeclarationRector extends AbstractRector
|
||||
return null;
|
||||
}
|
||||
|
||||
$types = $this->propertyTypeInferer->inferProperty($node);
|
||||
if ($types) {
|
||||
$this->docBlockManipulator->changeVarTag($node, $types);
|
||||
return $node;
|
||||
$type = $this->propertyTypeInferer->inferProperty($node);
|
||||
if ($type instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
$this->docBlockManipulator->changeVarTag($node, $type);
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
@ -3,44 +3,29 @@
|
||||
namespace Rector\TypeDeclaration;
|
||||
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\NullableType;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
|
||||
final class TypeDeclarationToStringConverter
|
||||
{
|
||||
/**
|
||||
* @var NameResolver
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $nameResolver;
|
||||
private $staticTypeMapper;
|
||||
|
||||
public function __construct(NameResolver $nameResolver)
|
||||
public function __construct(StaticTypeMapper $staticTypeMapper)
|
||||
{
|
||||
$this->nameResolver = $nameResolver;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveFunctionLikeReturnTypeToString(FunctionLike $functionLike): array
|
||||
public function resolveFunctionLikeReturnTypeToPHPStanType(FunctionLike $functionLike): Type
|
||||
{
|
||||
if ($functionLike->getReturnType() === null) {
|
||||
return [];
|
||||
$functionReturnType = $functionLike->getReturnType();
|
||||
if ($functionReturnType === null) {
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
$returnType = $functionLike->getReturnType();
|
||||
|
||||
$types = [];
|
||||
|
||||
$type = $returnType instanceof NullableType ? $returnType->type : $returnType;
|
||||
$result = $this->nameResolver->getName($type);
|
||||
if ($result !== null) {
|
||||
$types[] = $result;
|
||||
}
|
||||
|
||||
if ($returnType instanceof NullableType) {
|
||||
$types[] = 'null';
|
||||
}
|
||||
|
||||
return $types;
|
||||
return $this->staticTypeMapper->mapPhpParserNodePHPStanType($functionReturnType);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Rector\TypeDeclaration\TypeInferer;
|
||||
|
||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
||||
use Rector\NodeTypeResolver\StaticTypeMapper;
|
||||
use Rector\PhpParser\Node\Resolver\NameResolver;
|
||||
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
|
||||
@ -29,6 +30,11 @@ abstract class AbstractTypeInferer
|
||||
*/
|
||||
protected $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var TypeFactory
|
||||
*/
|
||||
protected $typeFactory;
|
||||
|
||||
/**
|
||||
* @required
|
||||
*/
|
||||
@ -36,11 +42,13 @@ abstract class AbstractTypeInferer
|
||||
CallableNodeTraverser $callableNodeTraverser,
|
||||
NameResolver $nameResolver,
|
||||
NodeTypeResolver $nodeTypeResolver,
|
||||
StaticTypeMapper $staticTypeMapper
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
TypeFactory $typeFactory
|
||||
): void {
|
||||
$this->callableNodeTraverser = $callableNodeTraverser;
|
||||
$this->nameResolver = $nameResolver;
|
||||
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->typeFactory = $typeFactory;
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,7 @@ use PHPStan\Type\Type;
|
||||
|
||||
final class AssignToPropertyTypeInferer extends AbstractTypeInferer
|
||||
{
|
||||
/**
|
||||
* @return Type[]
|
||||
*/
|
||||
public function inferPropertyInClassLike(string $propertyName, ClassLike $classLike): array
|
||||
public function inferPropertyInClassLike(string $propertyName, ClassLike $classLike): Type
|
||||
{
|
||||
$assignedExprStaticTypes = [];
|
||||
|
||||
@ -48,7 +45,7 @@ final class AssignToPropertyTypeInferer extends AbstractTypeInferer
|
||||
return null;
|
||||
});
|
||||
|
||||
return $this->filterOutDuplicatedTypes($assignedExprStaticTypes);
|
||||
return $this->typeFactory->createMixedPassedOrUnionType($assignedExprStaticTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,23 +73,4 @@ final class AssignToPropertyTypeInferer extends AbstractTypeInferer
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $types
|
||||
* @return Type[]
|
||||
*/
|
||||
private function filterOutDuplicatedTypes(array $types): array
|
||||
{
|
||||
if (count($types) === 1) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
$uniqueTypes = [];
|
||||
foreach ($types as $type) {
|
||||
$valueObjectHash = md5(serialize($type));
|
||||
$uniqueTypes[$valueObjectHash] = $type;
|
||||
}
|
||||
|
||||
return $uniqueTypes;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace Rector\TypeDeclaration\TypeInferer;
|
||||
|
||||
use PhpParser\Node\Param;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\TypeDeclaration\Contract\TypeInferer\ParamTypeInfererInterface;
|
||||
|
||||
final class ParamTypeInferer
|
||||
@ -20,18 +22,15 @@ final class ParamTypeInferer
|
||||
$this->paramTypeInferers = $paramTypeInferers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function inferParam(Param $param): array
|
||||
public function inferParam(Param $param): Type
|
||||
{
|
||||
foreach ($this->paramTypeInferers as $paramTypeInferers) {
|
||||
$types = $paramTypeInferers->inferParam($param);
|
||||
if ($types !== []) {
|
||||
return $types;
|
||||
$type = $paramTypeInferers->inferParam($param);
|
||||
if (! $type instanceof MixedType) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
return new MixedType();
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,9 @@ use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\Php\ReturnTypeInfo;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\PhpParser\Node\Manipulator\PropertyFetchManipulator;
|
||||
use Rector\TypeDeclaration\Contract\TypeInferer\ParamTypeInfererInterface;
|
||||
@ -35,15 +36,12 @@ final class GetterNodeParamTypeInferer extends AbstractTypeInferer implements Pa
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function inferParam(Param $param): array
|
||||
public function inferParam(Param $param): Type
|
||||
{
|
||||
/** @var Class_|null $classNode */
|
||||
$classNode = $param->getAttribute(AttributeKey::CLASS_NODE);
|
||||
if ($classNode === null) {
|
||||
return [];
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
/** @var ClassMethod $classMethod */
|
||||
@ -54,15 +52,15 @@ final class GetterNodeParamTypeInferer extends AbstractTypeInferer implements Pa
|
||||
|
||||
$propertyNames = $this->propertyFetchManipulator->getPropertyNamesOfAssignOfVariable($classMethod, $paramName);
|
||||
if ($propertyNames === []) {
|
||||
return [];
|
||||
return new MixedType();
|
||||
}
|
||||
|
||||
$returnTypeInfo = null;
|
||||
$returnType = new MixedType();
|
||||
|
||||
// resolve property assigns
|
||||
$this->callableNodeTraverser->traverseNodesWithCallable($classNode, function (Node $node) use (
|
||||
$propertyNames,
|
||||
&$returnTypeInfo
|
||||
&$returnType
|
||||
): ?int {
|
||||
if (! $node instanceof Return_ || $node->expr === null) {
|
||||
return null;
|
||||
@ -80,28 +78,16 @@ final class GetterNodeParamTypeInferer extends AbstractTypeInferer implements Pa
|
||||
return null;
|
||||
}
|
||||
|
||||
$methodReturnTypeInfo = $this->docBlockManipulator->getReturnTypeInfo($methodNode);
|
||||
if ($methodReturnTypeInfo === null) {
|
||||
$methodReturnType = $this->docBlockManipulator->getReturnType($methodNode);
|
||||
if ($methodReturnType instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnTypeInfo = $methodReturnTypeInfo;
|
||||
$returnType = $methodReturnType;
|
||||
|
||||
return NodeTraverser::STOP_TRAVERSAL;
|
||||
});
|
||||
|
||||
/** @var ReturnTypeInfo|null $returnTypeInfo */
|
||||
if ($returnTypeInfo === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$docTypes = $returnTypeInfo->getDocTypes();
|
||||
|
||||
// remove "[]" for the php doc nodes, as they will be already arrayed
|
||||
foreach ($docTypes as $key => $docType) {
|
||||
$docTypes[$key] = rtrim($docType, '[]');
|
||||
}
|
||||
|
||||
return $docTypes;
|
||||
return $returnType;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user