mirror of
https://github.com/rectorphp/rector.git
synced 2025-02-21 01:41:00 +01:00
Merge pull request #1948 from rectorphp/phpunit-data-provider
[PHPUnit] Add array call to data provider
This commit is contained in:
commit
aba57afec0
@ -1,3 +1,4 @@
|
||||
services:
|
||||
Rector\PHPUnit\Rector\MethodCall\RemoveExpectAnyFromMockRector: ~
|
||||
Rector\PHPUnit\Rector\Class_\AddSeeTestAnnotationRector: ~
|
||||
Rector\PHPUnit\Rector\Class_\ArrayArgumentInTestToDataProviderRector: ~
|
||||
|
5
ecs.yaml
5
ecs.yaml
@ -28,6 +28,8 @@ services:
|
||||
- 'PhpParser\NodeVisitor\NameResolver'
|
||||
- 'PhpParser\Node\*'
|
||||
- '*Data'
|
||||
- '*Recipe'
|
||||
- '*ValueObject'
|
||||
- 'PhpParser\Comment'
|
||||
- 'PhpParser\Lexer'
|
||||
- 'PhpParser\Comment\Doc'
|
||||
@ -38,7 +40,6 @@ services:
|
||||
- 'Rector\DependencyInjection\Loader\*'
|
||||
- 'Symplify\PackageBuilder\*'
|
||||
- 'Symfony\Component\Console\Input\*Input'
|
||||
- '*ValueObject'
|
||||
- 'PHPStan\Analyser\NameScope'
|
||||
|
||||
Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer:
|
||||
@ -110,10 +111,12 @@ parameters:
|
||||
|
||||
Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff:
|
||||
# tough logic
|
||||
- 'packages/PHPUnit/src/Rector/Class_/ArrayArgumentInTestToDataProviderRector.php'
|
||||
- 'packages/DoctrinePhpDocParser/src/Ast/PhpDoc/*/*TagValueNode.php'
|
||||
- 'packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/FqnNamePhpDocNodeDecorator.php'
|
||||
- 'packages/NodeTypeResolver/src/PHPStan/Type/StaticTypeAnalyzer.php'
|
||||
- 'src/NodeContainer/ParsedNodesByType.php'
|
||||
- 'packages/NodeTypeResolver/src/StaticTypeMapper.php'
|
||||
- 'packages/PHPStan/src/Rector/Node/RemoveNonExistingVarAnnotationRector.php'
|
||||
- 'packages/Architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php'
|
||||
- 'src/PhpParser/Node/Commander/NodeRemovingCommander.php'
|
||||
|
@ -10,7 +10,6 @@ use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
|
||||
@ -149,18 +148,6 @@ final class DocBlockManipulator
|
||||
return $phpDocInfo->hasTag($name);
|
||||
}
|
||||
|
||||
public function removeParamTagByName(Node $node, string $name): void
|
||||
{
|
||||
if ($node->getDocComment() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
|
||||
$this->removeParamTagByParameter($phpDocInfo, $name);
|
||||
|
||||
$this->updateNodeWithPhpDocInfo($node, $phpDocInfo);
|
||||
}
|
||||
|
||||
public function addTag(Node $node, PhpDocChildNode $phpDocChildNode): void
|
||||
{
|
||||
$phpDocChildNode = $this->attributeAwareNodeFactory->createFromNode($phpDocChildNode);
|
||||
@ -363,31 +350,6 @@ final class DocBlockManipulator
|
||||
}
|
||||
}
|
||||
|
||||
public function removeParamTagByParameter(PhpDocInfo $phpDocInfo, string $parameterName): void
|
||||
{
|
||||
$phpDocNode = $phpDocInfo->getPhpDocNode();
|
||||
|
||||
/** @var PhpDocTagNode[] $phpDocTagNodes */
|
||||
$phpDocTagNodes = $phpDocNode->getTagsByName('@param');
|
||||
|
||||
foreach ($phpDocTagNodes as $phpDocTagNode) {
|
||||
/** @var ParamTagValueNode|InvalidTagValueNode $paramTagValueNode */
|
||||
$paramTagValueNode = $phpDocTagNode->value;
|
||||
|
||||
$parameterName = '$' . ltrim($parameterName, '$');
|
||||
|
||||
// process invalid tag values
|
||||
if ($paramTagValueNode instanceof InvalidTagValueNode) {
|
||||
if ($paramTagValueNode->value === $parameterName) {
|
||||
$this->removeTagFromPhpDocNode($phpDocNode, $phpDocTagNode);
|
||||
}
|
||||
// process normal tag
|
||||
} elseif ($paramTagValueNode->parameterName === $parameterName) {
|
||||
$this->removeTagFromPhpDocNode($phpDocNode, $phpDocTagNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpDocTagNode|PhpDocTagValueNode $phpDocTagOrPhpDocTagValueNode
|
||||
*/
|
||||
|
137
packages/NodeTypeResolver/src/StaticTypeMapper.php
Normal file
137
packages/NodeTypeResolver/src/StaticTypeMapper.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\BooleanType;
|
||||
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\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareUnionTypeNode;
|
||||
use Rector\Exception\NotImplementedException;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\Php\PhpVersionProvider;
|
||||
|
||||
/**
|
||||
* Inspired by @see StaticTypeToStringResolver
|
||||
*/
|
||||
final class StaticTypeMapper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const PHP_VERSION_SCALAR_TYPES = '7.0';
|
||||
|
||||
/**
|
||||
* @var PhpVersionProvider
|
||||
*/
|
||||
private $phpVersionProvider;
|
||||
|
||||
public function __construct(PhpVersionProvider $phpVersionProvider)
|
||||
{
|
||||
$this->phpVersionProvider = $phpVersionProvider;
|
||||
}
|
||||
|
||||
public function mapPHPStanTypeToPHPStanPhpDocTypeNode(Type $currentPHPStanType): ?TypeNode
|
||||
{
|
||||
if ($currentPHPStanType instanceof UnionType) {
|
||||
$unionTypesNodes = [];
|
||||
foreach ($currentPHPStanType->getTypes() as $unionedType) {
|
||||
$unionTypesNodes[] = $this->mapPHPStanTypeToPHPStanPhpDocTypeNode($unionedType);
|
||||
}
|
||||
|
||||
return new AttributeAwareUnionTypeNode($unionTypesNodes);
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ArrayType) {
|
||||
$itemTypeNode = $this->mapPHPStanTypeToPHPStanPhpDocTypeNode($currentPHPStanType->getItemType());
|
||||
if ($itemTypeNode === null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
return new ArrayTypeNode($itemTypeNode);
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof IntegerType) {
|
||||
return new IdentifierTypeNode('int');
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof StringType) {
|
||||
return new IdentifierTypeNode('string');
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof FloatType) {
|
||||
return new IdentifierTypeNode('float');
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($currentPHPStanType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Identifier|Name|NullableType|null
|
||||
*/
|
||||
public function mapPHPStanTypeToPhpParserNode(Type $currentPHPStanType): ?Node
|
||||
{
|
||||
if ($currentPHPStanType instanceof IntegerType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_SCALAR_TYPES)) {
|
||||
return new Identifier('int');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof StringType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_SCALAR_TYPES)) {
|
||||
return new Identifier('string');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof BooleanType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_SCALAR_TYPES)) {
|
||||
return new Identifier('bool');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof FloatType) {
|
||||
if ($this->phpVersionProvider->isAtLeast(self::PHP_VERSION_SCALAR_TYPES)) {
|
||||
return new Identifier('float');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ArrayType) {
|
||||
return new Identifier('array');
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof ObjectType) {
|
||||
return new FullyQualified($currentPHPStanType->getClassName());
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof UnionType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($currentPHPStanType instanceof MixedType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($currentPHPStanType));
|
||||
}
|
||||
}
|
@ -81,6 +81,21 @@ final class StaticTypeToStringResolver
|
||||
$this->resolversByArgumentType = $callableCollectorPopulator->populate($resolvers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $staticTypes
|
||||
* @return string[]
|
||||
*/
|
||||
public function resolveTypes(array $staticTypes): array
|
||||
{
|
||||
$typesAsStrings = [];
|
||||
foreach ($staticTypes as $staticType) {
|
||||
$currentTypesAsStrings = $this->resolveObjectType($staticType);
|
||||
$typesAsStrings = array_merge($typesAsStrings, $currentTypesAsStrings);
|
||||
}
|
||||
|
||||
return array_unique($typesAsStrings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
|
@ -56,30 +56,6 @@ final class RemoveTest extends AbstractKernelTestCase
|
||||
yield [__DIR__ . '/RemoveSource/before.txt', '', '@var'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataForRemoveParamTagByParameter()
|
||||
*/
|
||||
public function testRemoveParamTagByParameter(
|
||||
string $phpDocBeforeFilePath,
|
||||
string $phpDocAfterFilePath,
|
||||
string $parameterName
|
||||
): void {
|
||||
$phpDocInfo = $this->createPhpDocInfoFromFile($phpDocBeforeFilePath);
|
||||
|
||||
$this->docBlockManipulator->removeParamTagByParameter($phpDocInfo, $parameterName);
|
||||
|
||||
$this->assertStringEqualsFile(
|
||||
$phpDocAfterFilePath,
|
||||
$this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo)
|
||||
);
|
||||
}
|
||||
|
||||
public function provideDataForRemoveParamTagByParameter(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/RemoveSource/before3.txt', __DIR__ . '/RemoveSource/after3.txt', 'paramName'];
|
||||
yield [__DIR__ . '/RemoveSource/before3.txt', __DIR__ . '/RemoveSource/after3.txt', '$paramName'];
|
||||
}
|
||||
|
||||
private function createPhpDocInfoFromFile(string $phpDocBeforeFilePath): PhpDocInfo
|
||||
{
|
||||
$phpDocBefore = FileSystem::read($phpDocBeforeFilePath);
|
||||
|
@ -30,31 +30,6 @@ final class DocBlockManipulatorTest extends AbstractKernelTestCase
|
||||
$this->assertFalse($this->docBlockManipulator->hasTag($node, 'var'));
|
||||
}
|
||||
|
||||
public function testRemoveAnnotationFromNode(): void
|
||||
{
|
||||
$node = $this->createNodeWithDoc('@param ParamType $paramName');
|
||||
|
||||
$this->assertNotSame('', $node->getDocComment()->getText());
|
||||
|
||||
$this->docBlockManipulator->removeTagFromNode($node, 'param');
|
||||
$this->assertNull($node->getDocComment());
|
||||
|
||||
$initDoc = <<<'CODE_SAMPLE'
|
||||
* @param ParamType $paramName
|
||||
* @param AnotherValue $anotherValue
|
||||
CODE_SAMPLE;
|
||||
$node = $this->createNodeWithDoc($initDoc);
|
||||
|
||||
$this->docBlockManipulator->removeParamTagByName($node, 'paramName');
|
||||
|
||||
$expectedDoc = <<<'CODE_SAMPLE'
|
||||
/**
|
||||
* @param AnotherValue $anotherValue
|
||||
*/
|
||||
CODE_SAMPLE;
|
||||
$this->assertSame($expectedDoc, $node->getDocComment()->getText());
|
||||
}
|
||||
|
||||
private function createNodeWithDoc(string $doc): String_
|
||||
{
|
||||
$node = new String_('string');
|
||||
|
@ -5,4 +5,4 @@ services:
|
||||
|
||||
Rector\PHPUnit\:
|
||||
resource: '../src'
|
||||
exclude: '../src/{Rector/**/*Rector.php}'
|
||||
exclude: '../src/{Rector/**/*Rector.php,ValueObject/*}'
|
||||
|
@ -0,0 +1,78 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPUnit\NodeFactory;
|
||||
|
||||
use PhpParser\BuilderFactory;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Yield_;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\NodeTypeResolver\StaticTypeToStringResolver;
|
||||
use Rector\PHPUnit\ValueObject\DataProviderClassMethodRecipe;
|
||||
|
||||
final class DataProviderClassMethodFactory
|
||||
{
|
||||
/**
|
||||
* @var BuilderFactory
|
||||
*/
|
||||
private $builderFactory;
|
||||
|
||||
/**
|
||||
* @var StaticTypeToStringResolver
|
||||
*/
|
||||
private $staticTypeToStringResolver;
|
||||
|
||||
/**
|
||||
* @var DocBlockManipulator
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
public function __construct(
|
||||
BuilderFactory $builderFactory,
|
||||
StaticTypeToStringResolver $staticTypeToStringResolver,
|
||||
DocBlockManipulator $docBlockManipulator
|
||||
) {
|
||||
$this->builderFactory = $builderFactory;
|
||||
$this->staticTypeToStringResolver = $staticTypeToStringResolver;
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
}
|
||||
|
||||
public function createFromRecipe(DataProviderClassMethodRecipe $dataProviderClassMethodRecipe): ClassMethod
|
||||
{
|
||||
$methodBuilder = $this->builderFactory->method($dataProviderClassMethodRecipe->getMethodName());
|
||||
$methodBuilder->makePublic();
|
||||
|
||||
$classMethod = $methodBuilder->getNode();
|
||||
|
||||
foreach ($dataProviderClassMethodRecipe->getArgs() as $arg) {
|
||||
$value = $arg->value;
|
||||
if ($value instanceof Array_) {
|
||||
foreach ($value->items as $arrayItem) {
|
||||
$returnStatement = new Yield_($arrayItem->value);
|
||||
$classMethod->stmts[] = new Expression($returnStatement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->decorateClassMethodWithReturnTypeAndTag($classMethod, $dataProviderClassMethodRecipe);
|
||||
|
||||
return $classMethod;
|
||||
}
|
||||
|
||||
private function decorateClassMethodWithReturnTypeAndTag(
|
||||
ClassMethod $classMethod,
|
||||
DataProviderClassMethodRecipe $dataProviderClassMethodRecipe
|
||||
): void {
|
||||
$classMethod->returnType = new Identifier('iterable');
|
||||
|
||||
$providedType = $dataProviderClassMethodRecipe->getProvidedType();
|
||||
if ($providedType === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$typesAsStrings = $this->staticTypeToStringResolver->resolveTypes([$providedType]);
|
||||
$this->docBlockManipulator->addReturnTag($classMethod, implode('|', $typesAsStrings));
|
||||
}
|
||||
}
|
@ -0,0 +1,449 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPUnit\Rector\Class_;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
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\StaticTypeToStringResolver;
|
||||
use Rector\PHPUnit\NodeFactory\DataProviderClassMethodFactory;
|
||||
use Rector\PHPUnit\ValueObject\DataProviderClassMethodRecipe;
|
||||
use Rector\PHPUnit\ValueObject\ParamAndArgValueObject;
|
||||
use Rector\Rector\AbstractPHPUnitRector;
|
||||
use Rector\RectorDefinition\ConfiguredCodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
|
||||
/**
|
||||
* @see \Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRector\ArrayArgumentInTestToDataProviderRectorTest
|
||||
*/
|
||||
final class ArrayArgumentInTestToDataProviderRector extends AbstractPHPUnitRector
|
||||
{
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $configuration = [];
|
||||
|
||||
/**
|
||||
* @var DocBlockManipulator
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @var StaticTypeToStringResolver
|
||||
*/
|
||||
private $staticTypeToStringResolver;
|
||||
|
||||
/**
|
||||
* @var StaticTypeMapper
|
||||
*/
|
||||
private $staticTypeMapper;
|
||||
|
||||
/**
|
||||
* @var DataProviderClassMethodRecipe[]
|
||||
*/
|
||||
private $dataProviderClassMethodRecipes = [];
|
||||
|
||||
/**
|
||||
* @var DataProviderClassMethodFactory
|
||||
*/
|
||||
private $dataProviderClassMethodFactory;
|
||||
|
||||
/**
|
||||
* @param mixed[] $configuration
|
||||
*/
|
||||
public function __construct(
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
StaticTypeToStringResolver $staticTypeToStringResolver,
|
||||
StaticTypeMapper $staticTypeMapper,
|
||||
DataProviderClassMethodFactory $dataProviderClassMethodFactory,
|
||||
array $configuration = []
|
||||
) {
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->staticTypeToStringResolver = $staticTypeToStringResolver;
|
||||
$this->staticTypeMapper = $staticTypeMapper;
|
||||
$this->dataProviderClassMethodFactory = $dataProviderClassMethodFactory;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Move array argument from tests into data provider [configurable]', [
|
||||
new ConfiguredCodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeServiceTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function test()
|
||||
{
|
||||
$this->doTestMultiple([1, 2, 3]);
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeServiceTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideDataForTest()
|
||||
*/
|
||||
public function test(int $value)
|
||||
{
|
||||
$this->doTestSingle($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function provideDataForTest(): iterable
|
||||
{
|
||||
yield 1;
|
||||
yield 2;
|
||||
yield 3;
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
|
||||
,
|
||||
[
|
||||
'$configuration' => [
|
||||
[
|
||||
'class' => 'PHPUnit\Framework\TestCase',
|
||||
'old_method' => 'doTestMultiple',
|
||||
'new_method' => 'doTestSingle',
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Class_::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_ $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if (! $this->isInTestClass($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->dataProviderClassMethodRecipes = [];
|
||||
|
||||
$this->traverseNodesWithCallable($node->stmts, function (Node $node) {
|
||||
if (! $node instanceof MethodCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->configuration as $singleConfiguration) {
|
||||
if (! $this->isMethodCallMatch($node, $singleConfiguration)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($node->args) !== 1) {
|
||||
throw new ShouldNotHappenException(__METHOD__);
|
||||
}
|
||||
|
||||
// resolve value types
|
||||
$firstArgumentValue = $node->args[0]->value;
|
||||
if (! $firstArgumentValue instanceof Array_) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
// rename method to new one handling non-array input
|
||||
$node->name = new Identifier($singleConfiguration['new_method']);
|
||||
|
||||
$dataProviderMethodName = $this->createDataProviderMethodName($node);
|
||||
|
||||
$this->dataProviderClassMethodRecipes[] = new DataProviderClassMethodRecipe(
|
||||
$dataProviderMethodName,
|
||||
$node->args,
|
||||
$this->resolveUniqueArrayStaticType($firstArgumentValue)
|
||||
);
|
||||
|
||||
$node->args = [];
|
||||
$paramAndArgs = $this->collectParamAndArgsFromArray($firstArgumentValue);
|
||||
foreach ($paramAndArgs as $paramAndArg) {
|
||||
$node->args[] = new Arg($paramAndArg->getVariable());
|
||||
}
|
||||
|
||||
/** @var ClassMethod $methodNode */
|
||||
$methodNode = $node->getAttribute(AttributeKey::METHOD_NODE);
|
||||
$this->refactorTestClassMethodParams($methodNode, $paramAndArgs);
|
||||
|
||||
// add data provider annotation
|
||||
$dataProviderTagNode = $this->createDataProviderTagNode($dataProviderMethodName);
|
||||
$this->docBlockManipulator->addTag($methodNode, $dataProviderTagNode);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if ($this->dataProviderClassMethodRecipes === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dataProviderClassMethods = $this->createDataProviderClassMethodsFromRecipes();
|
||||
|
||||
$node->stmts = array_Merge($node->stmts, $dataProviderClassMethods);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function createDataProviderTagNode(string $dataProviderMethodName): PhpDocTagNode
|
||||
{
|
||||
return new PhpDocTagNode('@dataProvider', new GenericTagValueNode($dataProviderMethodName . '()'));
|
||||
}
|
||||
|
||||
private function createParamTagNode(string $name, TypeNode $typeNode): PhpDocTagNode
|
||||
{
|
||||
return new PhpDocTagNode('@param', new ParamTagValueNode($typeNode, false, '$' . $name, ''));
|
||||
}
|
||||
|
||||
private function resolveUniqueArrayStaticTypes(Array_ $array): ?Type
|
||||
{
|
||||
$itemStaticTypes = [];
|
||||
foreach ($array->items as $arrayItem) {
|
||||
$arrayItemStaticType = $this->getStaticType($arrayItem->value);
|
||||
if ($arrayItemStaticType === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueObjectHash = implode('_', $this->staticTypeToStringResolver->resolveObjectType($arrayItemStaticType));
|
||||
|
||||
$itemStaticTypes[$valueObjectHash] = new ArrayType(new MixedType(), $arrayItemStaticType);
|
||||
}
|
||||
|
||||
$itemStaticTypes = array_values($itemStaticTypes);
|
||||
|
||||
if (count($itemStaticTypes) > 1) {
|
||||
return new UnionType($itemStaticTypes);
|
||||
}
|
||||
|
||||
if (count($itemStaticTypes) === 1) {
|
||||
return $itemStaticTypes[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function createDataProviderMethodName(Node $node): string
|
||||
{
|
||||
/** @var string $methodName */
|
||||
$methodName = $node->getAttribute(AttributeKey::METHOD_NAME);
|
||||
|
||||
return 'provideDataFor' . ucfirst($methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMethod[]
|
||||
*/
|
||||
private function createDataProviderClassMethodsFromRecipes(): array
|
||||
{
|
||||
$dataProviderClassMethods = [];
|
||||
|
||||
foreach ($this->dataProviderClassMethodRecipes as $dataProviderClassMethodRecipe) {
|
||||
$dataProviderClassMethods[] = $this->dataProviderClassMethodFactory->createFromRecipe(
|
||||
$dataProviderClassMethodRecipe
|
||||
);
|
||||
}
|
||||
|
||||
return $dataProviderClassMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ParamAndArgValueObject[]
|
||||
*/
|
||||
private function collectParamAndArgsFromArray(Array_ $array): array
|
||||
{
|
||||
// multiple arguments
|
||||
$i = 1;
|
||||
|
||||
$paramAndArgs = [];
|
||||
|
||||
$isNestedArray = $this->isNestedArray($array);
|
||||
|
||||
$itemsStaticType = $this->resolveItemStaticType($array, $isNestedArray);
|
||||
|
||||
if ($isNestedArray === false) {
|
||||
foreach ($array->items as $arrayItem) {
|
||||
$variable = new Variable('variable' . ($i === 1 ? '' : $i));
|
||||
|
||||
$paramAndArgs[] = new ParamAndArgValueObject($variable, $itemsStaticType);
|
||||
++$i;
|
||||
|
||||
if (! $arrayItem->value instanceof Array_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($array->items as $arrayItem) {
|
||||
/** @var Array_ $nestedArray */
|
||||
$nestedArray = $arrayItem->value;
|
||||
foreach ($nestedArray->items as $nestedArrayItem) {
|
||||
$variable = new Variable('variable' . ($i === 1 ? '' : $i));
|
||||
|
||||
$itemsStaticType = $this->getStaticType($nestedArrayItem->value);
|
||||
$paramAndArgs[] = new ParamAndArgValueObject($variable, $itemsStaticType);
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $paramAndArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ParamAndArgValueObject[] $paramAndArgs
|
||||
* @return Param[]
|
||||
*/
|
||||
private function createParams(array $paramAndArgs): array
|
||||
{
|
||||
$params = [];
|
||||
foreach ($paramAndArgs as $paramAndArg) {
|
||||
$param = new Param($paramAndArg->getVariable());
|
||||
|
||||
$staticType = $paramAndArg->getType();
|
||||
|
||||
if ($staticType !== null && ! $staticType instanceof UnionType) {
|
||||
$phpNodeType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($staticType);
|
||||
if ($phpNodeType !== null) {
|
||||
$param->type = $phpNodeType;
|
||||
}
|
||||
}
|
||||
|
||||
$params[] = $param;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $itemsStaticTypes
|
||||
* @return Type[]
|
||||
*/
|
||||
private function filterUniqueStaticTypes(array $itemsStaticTypes): array
|
||||
{
|
||||
$uniqueStaticTypes = [];
|
||||
foreach ($itemsStaticTypes as $itemsStaticType) {
|
||||
$uniqueHash = implode('_', $this->staticTypeToStringResolver->resolveObjectType($itemsStaticType));
|
||||
$uniqueHash = md5($uniqueHash);
|
||||
|
||||
$uniqueStaticTypes[$uniqueHash] = $itemsStaticType;
|
||||
}
|
||||
|
||||
return array_values($uniqueStaticTypes);
|
||||
}
|
||||
|
||||
private function resolveItemStaticType(Array_ $array, bool $isNestedArray): ?Type
|
||||
{
|
||||
$itemsStaticTypes = [];
|
||||
if ($isNestedArray === false) {
|
||||
foreach ($array->items as $arrayItem) {
|
||||
$arrayItemStaticType = $this->getStaticType($arrayItem->value);
|
||||
if ($arrayItemStaticType) {
|
||||
$itemsStaticTypes[] = $arrayItemStaticType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$itemsStaticTypes = $this->filterUniqueStaticTypes($itemsStaticTypes);
|
||||
|
||||
if ($itemsStaticTypes !== null && count($itemsStaticTypes) > 1) {
|
||||
return new UnionType($itemsStaticTypes);
|
||||
}
|
||||
|
||||
if (count($itemsStaticTypes) === 1) {
|
||||
return $itemsStaticTypes[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function isNestedArray(Array_ $array): bool
|
||||
{
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if ($arrayItem->value instanceof Array_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $singleConfiguration
|
||||
*/
|
||||
private function isMethodCallMatch(MethodCall $methodCall, array $singleConfiguration): bool
|
||||
{
|
||||
if (! $this->isType($methodCall->var, $singleConfiguration['class'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->isName($methodCall->name, $singleConfiguration['old_method']);
|
||||
}
|
||||
|
||||
private function resolveUniqueArrayStaticType(Array_ $array): ?Type
|
||||
{
|
||||
$isNestedArray = $this->isNestedArray($array);
|
||||
|
||||
$uniqueArrayStaticType = $this->resolveUniqueArrayStaticTypes($array);
|
||||
|
||||
if ($isNestedArray && $uniqueArrayStaticType instanceof ArrayType) {
|
||||
// unwrap one level up
|
||||
return $uniqueArrayStaticType->getItemType();
|
||||
}
|
||||
|
||||
return $uniqueArrayStaticType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ParamAndArgValueObject[] $paramAndArgs
|
||||
*/
|
||||
private function refactorTestClassMethodParams(ClassMethod $classMethod, array $paramAndArgs): void
|
||||
{
|
||||
$classMethod->params = $this->createParams($paramAndArgs);
|
||||
|
||||
foreach ($paramAndArgs as $paramAndArg) {
|
||||
$staticType = $paramAndArg->getType();
|
||||
|
||||
if (! $staticType instanceof UnionType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var string $paramName */
|
||||
$paramName = $this->getName($paramAndArg->getVariable());
|
||||
|
||||
/** @var TypeNode $staticTypeNode */
|
||||
$staticTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($staticType);
|
||||
|
||||
$paramTagNode = $this->createParamTagNode($paramName, $staticTypeNode);
|
||||
$this->docBlockManipulator->addTag($classMethod, $paramTagNode);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPUnit\ValueObject;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class DataProviderClassMethodRecipe
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
|
||||
/**
|
||||
* @var Arg[]
|
||||
*/
|
||||
private $args = [];
|
||||
|
||||
/**
|
||||
* @var Type|null
|
||||
*/
|
||||
private $providedType;
|
||||
|
||||
/**
|
||||
* @param Arg[] $args
|
||||
*/
|
||||
public function __construct(string $methodName, array $args, ?Type $providedType)
|
||||
{
|
||||
$this->methodName = $methodName;
|
||||
$this->args = $args;
|
||||
$this->providedType = $providedType;
|
||||
}
|
||||
|
||||
public function getMethodName(): string
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Arg[]
|
||||
*/
|
||||
public function getArgs(): array
|
||||
{
|
||||
return $this->args;
|
||||
}
|
||||
|
||||
public function getProvidedType(): ?Type
|
||||
{
|
||||
return $this->providedType;
|
||||
}
|
||||
}
|
35
packages/PHPUnit/src/ValueObject/ParamAndArgValueObject.php
Normal file
35
packages/PHPUnit/src/ValueObject/ParamAndArgValueObject.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPUnit\ValueObject;
|
||||
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PHPStan\Type\Type;
|
||||
|
||||
final class ParamAndArgValueObject
|
||||
{
|
||||
/**
|
||||
* @var Variable
|
||||
*/
|
||||
private $variable;
|
||||
|
||||
/**
|
||||
* @var Type|null
|
||||
*/
|
||||
private $type;
|
||||
|
||||
public function __construct(Variable $variable, ?Type $type)
|
||||
{
|
||||
$this->variable = $variable;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getVariable(): Variable
|
||||
{
|
||||
return $this->variable;
|
||||
}
|
||||
|
||||
public function getType(): ?Type
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
@ -8,5 +8,3 @@ namespace Rector\PHPUnit\Tests\Rector\Class_\AddSeeTestAnnotationRector\Fixture;
|
||||
class SomeExisting
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRector;
|
||||
|
||||
use Rector\PHPUnit\Rector\Class_\ArrayArgumentInTestToDataProviderRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class ArrayArgumentInTestToDataProviderRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/fixture.php.inc',
|
||||
__DIR__ . '/Fixture/various_types.php.inc',
|
||||
__DIR__ . '/Fixture/two_arguments.php.inc',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
protected function getRectorsWithConfiguration(): array
|
||||
{
|
||||
return [
|
||||
ArrayArgumentInTestToDataProviderRector::class => [
|
||||
'$configuration' => [
|
||||
[
|
||||
'class' => 'PHPUnit\Framework\TestCase',
|
||||
'old_method' => 'doTestMultiple',
|
||||
'new_method' => 'doTestSingle',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRector\Fixture;
|
||||
|
||||
class SomeServiceTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function test()
|
||||
{
|
||||
$this->doTestMultiple([1, 2, 3]);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRector\Fixture;
|
||||
|
||||
class SomeServiceTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideDataForTest()
|
||||
*/
|
||||
public function test(int $variable)
|
||||
{
|
||||
$this->doTestSingle($variable);
|
||||
}
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function provideDataForTest(): iterable
|
||||
{
|
||||
yield 1;
|
||||
yield 2;
|
||||
yield 3;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRector\Fixture;
|
||||
|
||||
class TwoArgumentsTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function test()
|
||||
{
|
||||
$this->doTestMultiple([['before', 'after']]);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRector\Fixture;
|
||||
|
||||
class TwoArgumentsTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideDataForTest()
|
||||
*/
|
||||
public function test(string $variable, string $variable2)
|
||||
{
|
||||
$this->doTestSingle($variable, $variable2);
|
||||
}
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function provideDataForTest(): iterable
|
||||
{
|
||||
yield ['before', 'after'];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRector\Fixture;
|
||||
|
||||
class VariousTypesTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function test()
|
||||
{
|
||||
$this->doTestMultiple([1, '2', 3.5]);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\PHPUnit\Tests\Rector\Class_\ArrayArgumentInTestToDataProviderRector\Fixture;
|
||||
|
||||
class VariousTypesTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @param int|float|string $variable
|
||||
* @dataProvider provideDataForTest()
|
||||
*/
|
||||
public function test($variable)
|
||||
{
|
||||
$this->doTestSingle($variable);
|
||||
}
|
||||
/**
|
||||
* @return float[]|int[]|string[]
|
||||
*/
|
||||
public function provideDataForTest(): iterable
|
||||
{
|
||||
yield 1;
|
||||
yield '2';
|
||||
yield 3.5;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -13,7 +13,4 @@ parameters:
|
||||
php_version_features: '7.1'
|
||||
|
||||
services:
|
||||
# Rector\Doctrine\Rector\Class_\AddUuidToEntityWhereMissingRector: ~
|
||||
# Rector\Doctrine\Rector\Class_\AddUuidMirrorForRelationPropertyRector: ~
|
||||
Rector\CodingStyle\Rector\Namespace_\ImportFullyQualifiedNamesRector: ~
|
||||
# Rector\PHPUnit\Rector\Class_\AddSeeTestAnnotationRector: ~
|
||||
|
Loading…
x
Reference in New Issue
Block a user