[Generics] Add GenericsPHPStormMethodAnnotationRector (#5365)

Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
Tomas Votruba 2021-01-29 17:24:20 +01:00 committed by GitHub
parent 033b2a38e9
commit 024ec4ba93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 657 additions and 147 deletions

View File

@ -1,7 +1,12 @@
{
"name": "rector/rector",
"description": "Instant upgrade and refactoring of your PHP code",
"keywords": ["instant upgrades", "instant refactoring", "ast", "automated refactoring"],
"keywords": [
"instant upgrades",
"instant refactoring",
"ast",
"automated refactoring"
],
"homepage": "https://getrector.org",
"license": "MIT",
"authors": [
@ -38,8 +43,8 @@
"sebastian/diff": "^4.0",
"symfony/cache": "^4.4.8|^5.1",
"symfony/console": "^4.4.8|^5.1",
"symfony/expression-language": "^4.4|^5.1",
"symfony/dependency-injection": "^5.1",
"symfony/expression-language": "^4.4|^5.1",
"symfony/finder": "^4.4.8|^5.1",
"symfony/http-kernel": "^4.4.8|^5.1",
"symfony/process": "^4.4.8|^5.1",
@ -49,7 +54,7 @@
"symplify/package-builder": "^9.0.34",
"symplify/rule-doc-generator": "^9.0.34",
"symplify/set-config-resolver": "^9.0.34",
"symplify/simple-php-doc-parser": "^9.0.34",
"symplify/simple-php-doc-parser": "^9.0",
"symplify/skipper": "^9.0.34",
"symplify/smart-file-system": "^9.0.34",
"symplify/symfony-php-config": "^9.0.34",
@ -176,7 +181,8 @@
"Rector\\Twig\\": "rules/twig/src",
"Rector\\TypeDeclaration\\": "rules/type-declaration/src",
"Rector\\VendorLocker\\": "packages/vendor-locker/src",
"Rector\\Carbon\\": "rules/carbon/src"
"Rector\\Carbon\\": "rules/carbon/src",
"Rector\\Generics\\": "rules/generics/src"
}
},
"autoload-dev": {
@ -300,7 +306,8 @@
"Rector\\Utils\\NodeDocumentationGenerator\\Tests\\": "utils/node-documentation-generator/tests",
"Rector\\Utils\\PHPStanTypeMapperChecker\\": "utils/phpstan-type-mapper-checker/src",
"Rector\\Utils\\ProjectValidator\\": "utils/project-validator/src",
"Rector\\Carbon\\Tests\\": "rules/carbon/tests"
"Rector\\Carbon\\Tests\\": "rules/carbon/tests",
"Rector\\Generics\\Tests\\": "rules/generics/tests"
}
},
"scripts": {

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
use Rector\Php71\Rector\Assign\AssignArrayToStringRector;
use Rector\Php71\Rector\BinaryOp\BinaryOpBetweenNumberAndStringRector;
use Rector\Php71\Rector\BinaryOp\IsIterableRector;
use Rector\Php71\Rector\BooleanOr\IsIterableRector;
use Rector\Php71\Rector\FuncCall\CountOnNullRector;
use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector;
use Rector\Php71\Rector\List_\ListToArrayDestructRector;

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
use Rector\Php73\Rector\BinaryOp\IsCountableRector;
use Rector\Php73\Rector\BooleanOr\IsCountableRector;
use Rector\Php73\Rector\ConstFetch\SensitiveConstantNameRector;
use Rector\Php73\Rector\FuncCall\ArrayKeyFirstLastRector;
use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector;

View File

@ -2,13 +2,13 @@
declare(strict_types=1);
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\PHPUnit\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector;
use Rector\PHPUnit\Rector\MethodCall\GetMockBuilderGetMockToCreateMockRector;
use Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
use Rector\Renaming\Rector\Name\RenameClassRector;
use Rector\Renaming\ValueObject\MethodCallRename;
use Rector\Renaming\ValueObject\PseudoNamespaceToNamespace;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;

View File

@ -2,9 +2,9 @@
declare(strict_types=1);
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Rector\Renaming\Rector\Name\RenameClassRector;
use Rector\Renaming\ValueObject\PseudoNamespaceToNamespace;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;

View File

@ -12024,7 +12024,7 @@ Changes `count()` on null to safe ternary check
Changes `is_array` + Traversable check to `is_iterable`
- class: `Rector\Php71\Rector\BinaryOp\IsIterableRector`
- class: `Rector\Php71\Rector\BooleanOr\IsIterableRector`
```diff
-is_array($foo) || $foo instanceof Traversable;
@ -12353,7 +12353,7 @@ Make use of `array_key_first()` and `array_key_last()`
Changes `is_array` + Countable check to `is_countable`
- class: `Rector\Php73\Rector\BinaryOp\IsCountableRector`
- class: `Rector\Php73\Rector\BooleanOr\IsCountableRector`
```diff
-is_array($foo) || $foo instanceof Countable;
@ -14197,7 +14197,7 @@ Replaces defined Pseudo_Namespaces by Namespace\Ones.
- class: `Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector`
```php
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\Renaming\ValueObject\PseudoNamespaceToNamespace;
use Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;

View File

@ -12,4 +12,13 @@ use Rector\BetterPhpDocParser\Contract\PhpDocNode\TypeAwareTagValueNodeInterface
final class AttributeAwareGenericTypeNode extends GenericTypeNode implements AttributeAwareNodeInterface, TypeAwareTagValueNodeInterface
{
use AttributeTrait;
public function __toString(): string
{
if ($this->genericTypes !== []) {
return parent::__toString();
}
return (string) $this->type;
}
}

View File

@ -6,10 +6,12 @@ namespace Rector\BetterPhpDocParser\PhpDocInfo;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
@ -39,12 +41,14 @@ use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType;
final class PhpDocInfo
{
/**
* @var array<string, string>
* @var array<class-string<PhpDocTagValueNode>, string>
*/
private const TAGS_TYPES_TO_NAMES = [
ReturnTagValueNode::class => '@return',
ParamTagValueNode::class => '@param',
VarTagValueNode::class => '@var',
MethodTagValueNode::class => '@method',
PropertyTagValueNode::class => '@property',
];
/**

View File

@ -11,7 +11,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\Renaming\ValueObject\PseudoNamespaceToNamespace;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Symplify\SimplePhpDocParser\PhpDocNodeTraverser;

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
@ -50,7 +51,12 @@ final class ObjectTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMa
}
if ($type instanceof GenericObjectType) {
$identifierTypeNode = new IdentifierTypeNode('\\' . $type->getClassName());
if (Strings::contains($type->getClassName(), '\\')) {
$name = '\\' . $type->getClassName();
} else {
$name = $type->getClassName();
}
$identifierTypeNode = new IdentifierTypeNode($name);
$genericTypeNodes = [];
foreach ($type->getTypes() as $genericType) {

View File

@ -7,9 +7,11 @@ namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use PhpParser\Node;
use PhpParser\Node\Name;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Generic\TemplateObjectWithoutClassType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareGenericTypeNode;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode;
use Rector\Core\Php\PhpVersionProvider;
use Rector\Core\ValueObject\PhpVersionFeature;
@ -44,6 +46,11 @@ final class ObjectWithoutClassTypeMapper implements TypeMapperInterface, PHPStan
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
if ($type instanceof TemplateObjectWithoutClassType) {
$attributeAwareIdentifierTypeNode = new AttributeAwareIdentifierTypeNode($type->getName());
return new AttributeAwareGenericTypeNode($attributeAwareIdentifierTypeNode, []);
}
return new AttributeAwareIdentifierTypeNode('object');
}

View File

@ -23,7 +23,7 @@ use Rector\StaticTypeMapper\ValueObject\Type\FalseBooleanType;
final class ScalarStringToTypeMapper
{
/**
* @var string[][]
* @var array<class-string<Type>, string[]>
*/
private const SCALAR_NAME_BY_TYPE = [
StringType::class => ['string'],

View File

@ -46,7 +46,6 @@ final class PhpDocTypeMapper
}
// fallback to PHPStan resolver
return $this->typeNodeResolver->resolve($typeNode, $nameScope);
}
}

View File

@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Generic\Contract;
interface IsAbleFuncCallInterface
{
public function getFuncName(): string;
public function getPhpVersion(): int;
public function getType(): string;
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->defaults()
->public()
->autowire()
->autoconfigure();
$services->load('Rector\Generics\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/../src/Rector']);
};

View File

@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace Rector\Generics\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\Generic\TemplateTypeHelper;
use Rector\Core\Rector\AbstractRector;
use Rector\Generics\Reflection\ClassGenericMethodResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see https://github.com/phpstan/phpstan/issues/3167
*
* @see \Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\GenericsPHPStormMethodAnnotationRectorTest
*/
final class GenericsPHPStormMethodAnnotationRector extends AbstractRector
{
/**
* @var ClassGenericMethodResolver
*/
private $classGenericMethodResolver;
public function __construct(ClassGenericMethodResolver $classGenericMethodResolver)
{
$this->classGenericMethodResolver = $classGenericMethodResolver;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Complete PHPStorm @method annotations, to make it understand the PHPStan/Psalm generics',
[
new CodeSample(
<<<'CODE_SAMPLE'
/**
* @template TEntity as object
*/
abstract class AbstractRepository
{
/**
* @return TEntity
*/
public function find($id)
{
}
}
/**
* @template TEntity as SomeObject
* @extends AbstractRepository<TEntity>
*/
final class AndroidDeviceRepository extends AbstractRepository
{
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
/**
* @template TEntity as object
*/
abstract class AbstractRepository
{
/**
* @return TEntity
*/
public function find($id)
{
}
}
/**
* @template TEntity as SomeObject
* @extends AbstractRepository<TEntity>
* @method SomeObject find($id)
*/
final class AndroidDeviceRepository extends AbstractRepository
{
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if ($node->extends === null) {
return null;
}
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return null;
}
$parentClassReflection = $classReflection->getParentClass();
if (! $parentClassReflection instanceof ClassReflection) {
return null;
}
if (! $parentClassReflection->isGeneric()) {
return null;
}
// resolve generic method from parent
$methodTagValueNodes = $this->classGenericMethodResolver->resolveFromClass($parentClassReflection);
$templateTypeMap = $classReflection->getTemplateTypeMap();
// @todo replace TTypes with specific types
foreach ($methodTagValueNodes as $methodTagValueNode) {
if ($methodTagValueNode->returnType === null) {
continue;
}
$returnType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType(
$methodTagValueNode->returnType,
$node
);
$resolvedType = TemplateTypeHelper::resolveTemplateTypes($returnType, $templateTypeMap);
$resolvedTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($resolvedType);
$methodTagValueNode->returnType = $resolvedTypeNode;
}
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
foreach ($methodTagValueNodes as $methodTagValueNode) {
$phpDocInfo->addTagValueNode($methodTagValueNode);
}
return $node;
}
}

View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Rector\Generics\Reflection;
use Nette\Utils\Strings;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use Rector\Generics\TagValueNodeFactory\MethodTagValueNodeFactory;
use Symplify\SimplePhpDocParser\SimplePhpDocParser;
use Symplify\SimplePhpDocParser\ValueObject\Ast\PhpDoc\SimplePhpDocNode;
final class ClassGenericMethodResolver
{
/**
* @var SimplePhpDocParser
*/
private $simplePhpDocParser;
/**
* @var MethodTagValueNodeFactory
*/
private $methodTagValueNodeFactory;
public function __construct(
SimplePhpDocParser $simplePhpDocParser,
MethodTagValueNodeFactory $methodTagValueNodeFactory
) {
$this->simplePhpDocParser = $simplePhpDocParser;
$this->methodTagValueNodeFactory = $methodTagValueNodeFactory;
}
/**
* @return MethodTagValueNode[]
*/
public function resolveFromClass(ClassReflection $classReflection): array
{
$methodTagValueNodes = [];
$templateNames = array_keys($classReflection->getTemplateTags());
foreach ($classReflection->getNativeMethods() as $methodReflection) {
$parentMethodDocComment = $methodReflection->getDocComment();
if ($parentMethodDocComment === null) {
continue;
}
// how to parse?
$parentMethodSimplePhpDocNode = $this->simplePhpDocParser->parseDocBlock($parentMethodDocComment);
$methodTagValueNode = $this->resolveMethodTagValueNode(
$parentMethodSimplePhpDocNode,
$templateNames,
$methodReflection
);
if (! $methodTagValueNode instanceof MethodTagValueNode) {
continue;
}
$methodTagValueNodes[] = $methodTagValueNode;
}
return $methodTagValueNodes;
}
/**
* @param string[] $templateNames
*/
private function resolveMethodTagValueNode(
SimplePhpDocNode $simplePhpDocNode,
array $templateNames,
MethodReflection $methodReflection
): ?MethodTagValueNode {
foreach ($simplePhpDocNode->getReturnTagValues() as $returnTagValueNode) {
foreach ($templateNames as $templateName) {
$typeAsString = (string) $returnTagValueNode->type;
if (! Strings::match($typeAsString, '#\b' . preg_quote($templateName, '#') . '\b#')) {
continue;
}
return $this->methodTagValueNodeFactory->createFromMethodReflectionAndReturnTagValueNode(
$methodReflection,
$returnTagValueNode
);
}
}
return null;
}
}

View File

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Rector\Generics\TagValueNodeFactory;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Type\Type;
use Rector\StaticTypeMapper\StaticTypeMapper;
final class MethodTagValueNodeFactory
{
/**
* @var MethodTagValueParameterNodeFactory
*/
private $methodTagValueParameterNodeFactory;
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;
public function __construct(
StaticTypeMapper $staticTypeMapper,
MethodTagValueParameterNodeFactory $methodTagValueParameterNodeFactory
) {
$this->methodTagValueParameterNodeFactory = $methodTagValueParameterNodeFactory;
$this->staticTypeMapper = $staticTypeMapper;
}
public function createFromMethodReflectionAndReturnTagValueNode(
MethodReflection $methodReflection,
ReturnTagValueNode $returnTagValueNode
): MethodTagValueNode {
$parameterReflections = $methodReflection->getVariants()[0]
->getParameters();
$stringParameters = $this->resolveStringParameters($parameterReflections);
$classReflection = $methodReflection->getDeclaringClass();
$templateTypeMap = $classReflection->getTemplateTypeMap();
$returnTagTypeNode = $returnTagValueNode->type;
if ($returnTagValueNode->type instanceof IdentifierTypeNode) {
$typeName = $returnTagValueNode->type->name;
$genericType = $templateTypeMap->getType($typeName);
if ($genericType instanceof Type) {
$returnTagType = $genericType;
$returnTagTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($returnTagType);
}
}
return new MethodTagValueNode(
false,
$returnTagTypeNode,
$methodReflection->getName(),
$stringParameters,
''
);
}
/**
* @param ParameterReflection[] $parameterReflections
* @return MethodTagValueParameterNode[]
*/
private function resolveStringParameters(array $parameterReflections): array
{
$stringParameters = [];
foreach ($parameterReflections as $parameterReflection) {
$stringParameters[] = $this->methodTagValueParameterNodeFactory->createFromParamReflection(
$parameterReflection
);
}
return $stringParameters;
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Rector\Generics\TagValueNodeFactory;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Type\MixedType;
use Rector\StaticTypeMapper\StaticTypeMapper;
final class MethodTagValueParameterNodeFactory
{
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;
public function __construct(StaticTypeMapper $staticTypeMapper)
{
$this->staticTypeMapper = $staticTypeMapper;
}
public function createFromParamReflection(ParameterReflection $parameterReflection): MethodTagValueParameterNode
{
$parameterType = $parameterReflection->getType();
if ($parameterType instanceof MixedType && ! $parameterType->isExplicitMixed()) {
$parameterTypeNode = null;
} else {
$parameterTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($parameterType);
}
return new MethodTagValueParameterNode(
$parameterTypeNode,
$parameterReflection->passedByReference()
->yes(),
$parameterReflection->isVariadic(),
'$' . $parameterReflection->getName(),
// @todo resolve
null
);
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
/**
* @template TEntity as object
*/
abstract class AbstractRepository
{
/**
* @return TEntity
*/
public function find($id)
{
}
}
/**
* @template TEntity as RealObject
* @extends AbstractRepository<TEntity>
*/
final class AndroidDeviceRepository extends AbstractRepository
{
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Fixture;
use Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source\RealObject;
/**
* @template TEntity as object
*/
abstract class AbstractRepository
{
/**
* @return TEntity
*/
public function find($id)
{
}
}
/**
* @template TEntity as RealObject
* @extends AbstractRepository<TEntity>
* @method RealObject find($id)
*/
final class AndroidDeviceRepository extends AbstractRepository
{
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector;
use Iterator;
use Rector\Generics\Rector\Class_\GenericsPHPStormMethodAnnotationRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class GenericsPHPStormMethodAnnotationRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return GenericsPHPStormMethodAnnotationRector::class;
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Generics\Tests\Rector\Class_\GenericsPHPStormMethodAnnotationRector\Source;
final class RealObject
{
}

View File

@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Php71\Rector\BinaryOp;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Generic\Rector\AbstractIsAbleFunCallRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector\IsIterableRectorTest
*/
final class IsIterableRector extends AbstractIsAbleFunCallRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Changes is_array + Traversable check to is_iterable',
[new CodeSample('is_array($foo) || $foo instanceof Traversable;', 'is_iterable($foo);')]);
}
public function getFuncName(): string
{
return 'is_iterable';
}
public function getPhpVersion(): int
{
return PhpVersionFeature::IS_ITERABLE;
}
public function getType(): string
{
return 'Traversable';
}
}

View File

@ -2,15 +2,20 @@
declare(strict_types=1);
namespace Rector\Generic\Rector;
namespace Rector\Php71\Rector\BooleanOr;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use Rector\Core\Rector\AbstractRector;
use Rector\Generic\Contract\IsAbleFuncCallInterface;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php71\IsArrayAndDualCheckToAble;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
abstract class AbstractIsAbleFunCallRector extends AbstractRector implements IsAbleFuncCallInterface
/**
* @see \Rector\Php71\Tests\Rector\BooleanOr\IsIterableRector\IsIterableRectorTest
*/
final class IsIterableRector extends AbstractRector
{
/**
* @var IsArrayAndDualCheckToAble
@ -22,6 +27,13 @@ abstract class AbstractIsAbleFunCallRector extends AbstractRector implements IsA
$this->isArrayAndDualCheckToAble = $isArrayAndDualCheckToAble;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Changes is_array + Traversable check to is_iterable',
[new CodeSample('is_array($foo) || $foo instanceof Traversable;', 'is_iterable($foo);')]);
}
/**
* @return string[]
*/
@ -39,19 +51,15 @@ abstract class AbstractIsAbleFunCallRector extends AbstractRector implements IsA
return null;
}
return $this->isArrayAndDualCheckToAble->processBooleanOr(
$node,
$this->getType(),
$this->getFuncName()
) ?: $node;
return $this->isArrayAndDualCheckToAble->processBooleanOr($node, 'Traversable', 'is_iterable') ?: $node;
}
private function shouldSkip(): bool
{
if (function_exists($this->getFuncName())) {
if (function_exists('is_iterable')) {
return false;
}
return $this->isAtLeastPhpVersion($this->getPhpVersion());
return ! $this->isAtLeastPhpVersion(PhpVersionFeature::IS_ITERABLE);
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector\Fixture;
namespace Rector\Php71\Tests\Rector\BooleanOr\IsIterableRector\Fixture;
use Traversable;
@ -19,7 +19,7 @@ function isIterable()
-----
<?php
namespace Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector\Fixture;
namespace Rector\Php71\Tests\Rector\BooleanOr\IsIterableRector\Fixture;
use Traversable;

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector\Fixture;
namespace Rector\Php71\Tests\Rector\BooleanOr\IsIterableRector\Fixture;
use Traversable;
@ -24,7 +24,7 @@ if (! function_exists('is_iterable')) {
-----
<?php
namespace Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector\Fixture;
namespace Rector\Php71\Tests\Rector\BooleanOr\IsIterableRector\Fixture;
use Traversable;

View File

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector;
namespace Rector\Php71\Tests\Rector\BooleanOr\IsIterableRector;
use Iterator;
use Rector\Php71\Rector\BinaryOp\IsIterableRector;
use Rector\Php71\Rector\BooleanOr\IsIterableRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

View File

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector;
namespace Rector\Php71\Tests\Rector\BooleanOr\IsIterableRector;
use Iterator;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php71\Rector\BinaryOp\IsIterableRector;
use Rector\Php71\Rector\BooleanOr\IsIterableRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

View File

@ -1,49 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Php73\Rector\BinaryOp;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Generic\Rector\AbstractIsAbleFunCallRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Php73\Tests\Rector\BinaryOp\IsCountableRector\IsCountableRectorTest
*/
final class IsCountableRector extends AbstractIsAbleFunCallRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Changes is_array + Countable check to is_countable',
[
new CodeSample(
<<<'CODE_SAMPLE'
is_array($foo) || $foo instanceof Countable;
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
is_countable($foo);
CODE_SAMPLE
),
]
);
}
public function getType(): string
{
return 'Countable';
}
public function getFuncName(): string
{
return 'is_countable';
}
public function getPhpVersion(): int
{
return PhpVersionFeature::IS_COUNTABLE;
}
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Rector\Php73\Rector\BooleanOr;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php71\IsArrayAndDualCheckToAble;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Php73\Tests\Rector\BinaryOr\IsCountableRector\IsCountableRectorTest
*/
final class IsCountableRector extends AbstractRector
{
/**
* @var IsArrayAndDualCheckToAble
*/
private $isArrayAndDualCheckToAble;
public function __construct(IsArrayAndDualCheckToAble $isArrayAndDualCheckToAble)
{
$this->isArrayAndDualCheckToAble = $isArrayAndDualCheckToAble;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Changes is_array + Countable check to is_countable',
[
new CodeSample(
<<<'CODE_SAMPLE'
is_array($foo) || $foo instanceof Countable;
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
is_countable($foo);
CODE_SAMPLE
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [BooleanOr::class];
}
/**
* @param BooleanOr $node
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkip()) {
return null;
}
return $this->isArrayAndDualCheckToAble->processBooleanOr($node, 'Countable', 'is_countable') ?: $node;
}
private function shouldSkip(): bool
{
if (function_exists('is_countable')) {
return false;
}
return $this->isAtLeastPhpVersion(PhpVersionFeature::IS_COUNTABLE);
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php73\Tests\Rector\BinaryOp\IsCountableRector\Fixture;
namespace Rector\Php73\Tests\Rector\BinaryOr\IsCountableRector\Fixture;
class Fixture73
{
@ -14,7 +14,7 @@ class Fixture73
-----
<?php
namespace Rector\Php73\Tests\Rector\BinaryOp\IsCountableRector\Fixture;
namespace Rector\Php73\Tests\Rector\BinaryOr\IsCountableRector\Fixture;
class Fixture73
{

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Php73\Tests\Rector\BinaryOp\IsCountableRector\Fixture;
namespace Rector\Php73\Tests\Rector\BinaryOr\IsCountableRector\Fixture;
use Countable;
@ -24,7 +24,7 @@ if (! function_exists('is_countable')) {
-----
<?php
namespace Rector\Php73\Tests\Rector\BinaryOp\IsCountableRector\Fixture;
namespace Rector\Php73\Tests\Rector\BinaryOr\IsCountableRector\Fixture;
use Countable;

View File

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Rector\Php73\Tests\Rector\BinaryOp\IsCountableRector;
namespace Rector\Php73\Tests\Rector\BinaryOr\IsCountableRector;
use Iterator;
use Rector\Php73\Rector\BinaryOp\IsCountableRector;
use Rector\Php73\Rector\BooleanOr\IsCountableRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

View File

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Rector\Php73\Tests\Rector\BinaryOp\IsCountableRector;
namespace Rector\Php73\Tests\Rector\BinaryOr\IsCountableRector;
use Iterator;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\Php73\Rector\BinaryOp\IsCountableRector;
use Rector\Php73\Rector\BooleanOr\IsCountableRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

View File

@ -18,9 +18,9 @@ use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\Util\StaticInstanceOf;
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PhpDoc\PhpDocTypeRenamer;
use Rector\Renaming\ValueObject\PseudoNamespaceToNamespace;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\Generic\ValueObject;
namespace Rector\Renaming\ValueObject;
final class PseudoNamespaceToNamespace
{

View File

@ -5,8 +5,8 @@ declare(strict_types=1);
namespace Rector\Renaming\Tests\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Iterator;
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Rector\Renaming\ValueObject\PseudoNamespaceToNamespace;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;