Skip to content

Commit

Permalink
[PHPUnit 10] Add AssertIssetToAssertObjectHasPropertyRector
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jun 22, 2024
1 parent f0d26e4 commit 1e207dd
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 50 deletions.
2 changes: 2 additions & 0 deletions config/sets/phpunit100.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Rector\PHPUnit\PHPUnit100\Rector\Class_\AddProphecyTraitRector;
use Rector\PHPUnit\PHPUnit100\Rector\Class_\PublicDataProviderClassMethodRector;
use Rector\PHPUnit\PHPUnit100\Rector\Class_\StaticDataProviderClassMethodRector;
use Rector\PHPUnit\PHPUnit100\Rector\MethodCall\AssertIssetToAssertObjectHasPropertyRector;
use Rector\PHPUnit\PHPUnit100\Rector\MethodCall\RemoveSetMethodsMethodCallRector;
use Rector\PHPUnit\Rector\StmtsAwareInterface\WithConsecutiveRector;
use Rector\PHPUnit\Set\PHPUnitSetList;
Expand All @@ -16,6 +17,7 @@
$rectorConfig->sets([PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES]);

$rectorConfig->rules([
AssertIssetToAssertObjectHasPropertyRector::class,
StaticDataProviderClassMethodRector::class,
PublicDataProviderClassMethodRector::class,
AddProphecyTraitRector::class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\Tests\PHPUnit100\Rector\MethodCall\AssertIssetToAssertObjectHasPropertyRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class AssertIssetToAssertObjectHasPropertyRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Rector\PHPUnit\Tests\PHPUnit100\Rector\MethodCall\AssertIssetToAssertObjectHasPropertyRector\Fixture;

use PHPUnit\Framework\TestCase;

final class SomeIssetToProperty extends TestCase
{
public function test()
{
$object = new \stdClass();
$this->assertTrue(isset($object->someProperty));
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\PHPUnit100\Rector\MethodCall\AssertIssetToAssertObjectHasPropertyRector\Fixture;

use PHPUnit\Framework\TestCase;

final class SomeIssetToProperty extends TestCase
{
public function test()
{
$object = new \stdClass();
$this->assertObjectHasAttribute('someProperty', $object);
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\PHPUnit\PHPUnit100\Rector\MethodCall\AssertIssetToAssertObjectHasPropertyRector;

return RectorConfig::configure()
->withRules([AssertIssetToAssertObjectHasPropertyRector::class]);
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@
namespace Rector\PHPUnit\CodeQuality\Rector\MethodCall;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\TypeWithClassName;
use Rector\PHPUnit\Enum\AssertMethod;
use Rector\PHPUnit\NodeAnalyzer\IdentifierManipulator;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\Reflection\ClassReflectionAnalyzer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

Expand All @@ -25,20 +21,9 @@
*/
final class AssertIssetToSpecificMethodRector extends AbstractRector
{
/**
* @var string
*/
private const ASSERT_TRUE = 'assertTrue';

/**
* @var string
*/
private const ASSERT_FALSE = 'assertFalse';

public function __construct(
private readonly IdentifierManipulator $identifierManipulator,
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
private readonly ClassReflectionAnalyzer $classReflectionAnalyzer
) {
}

Expand Down Expand Up @@ -68,60 +53,38 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, [self::ASSERT_TRUE, self::ASSERT_FALSE])) {
if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames(
$node,
[AssertMethod::ASSERT_TRUE, AssertMethod::ASSERT_FALSE]
)) {
return null;
}

if ($node->isFirstClassCallable()) {
return null;
}

$firstArgumentValue = $node->getArgs()[0]
->value;
$firstArg = $node->getArgs()[0];
$firstArgumentValue = $firstArg->value;

// is property access
if (! $firstArgumentValue instanceof Isset_) {
return null;
}

$variableNodeClass = $firstArgumentValue->vars[0];
if (! $variableNodeClass instanceof ArrayDimFetch) {
$issetVariable = $firstArgumentValue->vars[0];
if (! $issetVariable instanceof ArrayDimFetch) {
return null;
}

$issetNodeArg = $firstArgumentValue->vars[0];
return $this->refactorArrayDimFetchNode($node, $issetNodeArg);
}

private function hasMagicIsset(Expr $expr): bool
{
$type = $this->nodeTypeResolver->getType($expr);

if (! $type instanceof TypeWithClassName) {
// object not found, skip
return $type instanceof ObjectWithoutClassType;
}

$classReflection = $type->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}

if ($classReflection->hasMethod('__isset')) {
return true;
}

if (! $classReflection->isClass()) {
return false;
}

return $this->classReflectionAnalyzer->resolveParentClassName($classReflection) !== null;
return $this->refactorArrayDimFetchNode($node, $issetVariable);
}

private function refactorArrayDimFetchNode(MethodCall|StaticCall $node, ArrayDimFetch $arrayDimFetch): Node
{
$this->identifierManipulator->renameNodeWithMap($node, [
self::ASSERT_TRUE => 'assertArrayHasKey',
self::ASSERT_FALSE => 'assertArrayNotHasKey',
AssertMethod::ASSERT_TRUE => 'assertArrayHasKey',
AssertMethod::ASSERT_FALSE => 'assertArrayNotHasKey',
]);

$oldArgs = $node->getArgs();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\PHPUnit100\Rector\MethodCall;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\TypeWithClassName;
use Rector\PHPUnit\Enum\AssertMethod;
use Rector\PHPUnit\NodeAnalyzer\IdentifierManipulator;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\Reflection\ClassReflectionAnalyzer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\PHPUnit\Tests\PHPUnit100\Rector\MethodCall\AssertIssetToAssertObjectHasPropertyRector\AssertIssetToAssertObjectHasPropertyRectorTest
*
* @changelog https://github.com/sebastianbergmann/phpunit/issues/5220
*/
final class AssertIssetToAssertObjectHasPropertyRector extends AbstractRector
{
public function __construct(
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
private readonly IdentifierManipulator $identifierManipulator,
private readonly ClassReflectionAnalyzer $classReflectionAnalyzer,
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change "isset()" to check property, to assertObjectHasAttribute()method', [
new CodeSample(
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
public function test()
{
$object = new stdClass();
$this->assertTrue(isset($object->someProperty));
}
}
CODE_SAMPLE

,
<<<'CODE_SAMPLE'
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
public function test()
{
$object = new stdClass();
$this->assertObjectHasProperty('someProperty', $object);
}
}
CODE_SAMPLE
),
]);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [MethodCall::class, StaticCall::class];
}

/**
* @param MethodCall|StaticCall $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames(
$node,
[AssertMethod::ASSERT_TRUE, AssertMethod::ASSERT_FALSE]
)) {
return null;
}

if ($node->isFirstClassCallable()) {
return null;
}

$firstArg = $node->getArgs()[0];
$firstArgValue = $firstArg->value;
if (! $firstArgValue instanceof Isset_) {
return null;
}

$issetExpr = $firstArgValue->vars[0];
if (! $issetExpr instanceof PropertyFetch) {
return null;
}

if ($this->hasMagicIsset($issetExpr->var)) {
return null;
}

$name = $this->getName($issetExpr);
if ($name === null) {
return null;
}

$this->identifierManipulator->renameNodeWithMap($node, [
AssertMethod::ASSERT_TRUE => 'assertObjectHasAttribute',
AssertMethod::ASSERT_FALSE => 'assertObjectNotHasAttribute',
]);

$oldArgs = $node->getArgs();
unset($oldArgs[0]);

$newArgs = $this->nodeFactory->createArgs([new Node\Scalar\String_($name), $issetExpr->var]);
$node->args = [...$newArgs, ...$oldArgs];
return $node;
}

private function hasMagicIsset(Expr $expr): bool
{
$type = $this->nodeTypeResolver->getType($expr);
if (! $type instanceof TypeWithClassName) {
// object not found, skip
return $type instanceof ObjectWithoutClassType;
}

$classReflection = $type->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}

if ($classReflection->hasMethod('__isset')) {
return true;
}

if (! $classReflection->isClass()) {
return false;
}

return $this->classReflectionAnalyzer->resolveParentClassName($classReflection) !== null;
}
}
18 changes: 18 additions & 0 deletions src/Enum/AssertMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Rector\PHPUnit\Enum;

final class AssertMethod
{
/**
* @var string
*/
public const ASSERT_FALSE = 'assertFalse';

/**
* @var string
*/
public const ASSERT_TRUE = 'assertTrue';
}

0 comments on commit 1e207dd

Please sign in to comment.