diff --git a/config/sets/level/deprecated-level-set.php b/config/sets/level/deprecated-level-set.php
deleted file mode 100644
index 4155f094..00000000
--- a/config/sets/level/deprecated-level-set.php
+++ /dev/null
@@ -1,11 +0,0 @@
-sets([PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES]);
$rectorConfig->rules([
+ AssertIssetToAssertObjectHasPropertyRector::class,
StaticDataProviderClassMethodRector::class,
PublicDataProviderClassMethodRector::class,
- PropertyExistsWithoutAssertRector::class,
AddProphecyTraitRector::class,
WithConsecutiveRector::class,
RemoveSetMethodsMethodCallRector::class,
@@ -29,6 +29,10 @@
// https://github.com/sebastianbergmann/phpunit/issues/4087
new MethodCallRename('PHPUnit\Framework\Assert', 'assertRegExp', 'assertMatchesRegularExpression'),
+ // https://github.com/sebastianbergmann/phpunit/issues/5220
+ new MethodCallRename('PHPUnit\Framework\Assert', 'assertObjectHasAttribute', 'assertObjectHasProperty'),
+ new MethodCallRename('PHPUnit\Framework\Assert', 'assertObjectNotHasAttribute', 'assertObjectHasNotProperty'),
+
new MethodCallRename(
'PHPUnit\Framework\MockObject\Rule\InvocationOrder',
'getInvocationCount',
diff --git a/config/sets/phpunit110.php b/config/sets/phpunit110.php
new file mode 100644
index 00000000..6b580784
--- /dev/null
+++ b/config/sets/phpunit110.php
@@ -0,0 +1,10 @@
+rules([NamedArgumentForDataProviderRector::class]);
+};
diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md
index e6b90ea8..64e457a3 100644
--- a/docs/rector_rules_overview.md
+++ b/docs/rector_rules_overview.md
@@ -270,22 +270,37 @@ Turns instanceof comparisons to their method name alternatives in PHPUnit TestCa
-## AssertIssetToSpecificMethodRector
+## AssertIssetToAssertObjectHasPropertyRector
-Turns isset comparisons to their method name alternatives in PHPUnit TestCase
+Change `"isset()"` property check, to `assertObjectHasProperty()` method
-- class: [`Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertIssetToSpecificMethodRector`](../rules/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector.php)
+- class: [`Rector\PHPUnit\PHPUnit100\Rector\MethodCall\AssertIssetToAssertObjectHasPropertyRector`](../rules/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector.php)
```diff
--$this->assertTrue(isset($anything->foo));
-+$this->assertObjectHasAttribute("foo", $anything);
+ use PHPUnit\Framework\TestCase;
+
+ final class SomeTest extends TestCase
+ {
+ public function test()
+ {
+ $object = new stdClass();
+- $this->assertTrue(isset($object->someProperty));
++ $this->assertObjectHasProperty('someProperty', $object);
+ }
+ }
```
+## AssertIssetToSpecificMethodRector
+
+Turns `assertTrue()` + `isset()` comparisons to more precise `assertArrayHasKey()` method
+
+- class: [`Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertIssetToSpecificMethodRector`](../rules/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector.php)
+
```diff
--$this->assertFalse(isset($anything["foo"]), "message");
-+$this->assertArrayNotHasKey("foo", $anything, "message");
+-$this->assertTrue(isset($anything["foo"]), "message");
++$this->assertArrayHasKey("foo", $anything, "message");
```
@@ -751,21 +766,6 @@ Changes PHPUnit calls from self::assert*() to `$this->assert*()`
-## PropertyExistsWithoutAssertRector
-
-Turns PHPUnit TestCase assertObjectHasAttribute into `property_exists` comparisons
-
-- class: [`Rector\PHPUnit\PHPUnit100\Rector\MethodCall\PropertyExistsWithoutAssertRector`](../rules/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector.php)
-
-```diff
--$this->assertClassHasAttribute("property", "Class");
--$this->assertClassNotHasAttribute("property", "Class");
-+$this->assertFalse(property_exists(new Class, "property"));
-+$this->assertTrue(property_exists(new Class, "property"));
-```
-
-
-
## PublicDataProviderClassMethodRector
Change data provider methods to public
diff --git a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/fixture.php.inc b/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/fixture.php.inc
index ba678bd3..a5855b2e 100644
--- a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/fixture.php.inc
+++ b/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/fixture.php.inc
@@ -6,8 +6,6 @@ final class MyIssetTest extends \PHPUnit\Framework\TestCase
{
public function test()
{
- $this->assertTrue(isset($node->value1));
- $this->assertFalse(isset($node->value2), 'message');
$this->assertTrue(isset($node['value1']), 'message');
$this->assertFalse(isset($node['value2']));
}
@@ -23,8 +21,6 @@ final class MyIssetTest extends \PHPUnit\Framework\TestCase
{
public function test()
{
- $this->assertObjectHasAttribute('value1', $node);
- $this->assertObjectNotHasAttribute('value2', $node, 'message');
$this->assertArrayHasKey('value1', $node, 'message');
$this->assertArrayNotHasKey('value2', $node);
}
diff --git a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/magic_method_set_is_ignored.php.inc b/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/magic_method_set_is_ignored.php.inc
deleted file mode 100644
index 057d33f6..00000000
--- a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/magic_method_set_is_ignored.php.inc
+++ /dev/null
@@ -1,37 +0,0 @@
-assertTrue(isset($foo->bar));
- }
-}
-?>
------
-assertObjectHasAttribute('bar', $foo);
- }
-}
-?>
diff --git a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_if_magic_method_isset_exists_in_parent.php.inc b/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_if_magic_method_isset_exists_in_parent.php.inc
deleted file mode 100644
index 1ab766d3..00000000
--- a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_if_magic_method_isset_exists_in_parent.php.inc
+++ /dev/null
@@ -1,16 +0,0 @@
-assertTrue(isset($foo->bar));
- }
-}
diff --git a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_property.php.inc b/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_property.php.inc
new file mode 100644
index 00000000..b8abcb75
--- /dev/null
+++ b/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_property.php.inc
@@ -0,0 +1,12 @@
+assertTrue(isset($node->value1));
+ $this->assertFalse(isset($node->value2), 'message');
+ }
+}
diff --git a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/PropertyExistsWithoutAssertRectorTest.php b/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/AssertIssetToAssertObjectHasPropertyRectorTest.php
similarity index 73%
rename from rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/PropertyExistsWithoutAssertRectorTest.php
rename to rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/AssertIssetToAssertObjectHasPropertyRectorTest.php
index ac544b8c..c86b9d73 100644
--- a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/PropertyExistsWithoutAssertRectorTest.php
+++ b/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/AssertIssetToAssertObjectHasPropertyRectorTest.php
@@ -2,13 +2,13 @@
declare(strict_types=1);
-namespace Rector\PHPUnit\Tests\PHPUnit100\Rector\MethodCall\PropertyExistsWithoutAssertRector;
+namespace Rector\PHPUnit\Tests\PHPUnit100\Rector\MethodCall\AssertIssetToAssertObjectHasPropertyRector;
use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
-final class PropertyExistsWithoutAssertRectorTest extends AbstractRectorTestCase
+final class AssertIssetToAssertObjectHasPropertyRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
diff --git a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_if_magic_method_isset_exists.php.inc b/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/Fixture/skip_magic_isset.php.inc
similarity index 50%
rename from rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_if_magic_method_isset_exists.php.inc
rename to rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/Fixture/skip_magic_isset.php.inc
index b81ea622..14b0f1a3 100644
--- a/rules-tests/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector/Fixture/skip_if_magic_method_isset_exists.php.inc
+++ b/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/Fixture/skip_magic_isset.php.inc
@@ -1,14 +1,15 @@
assertTrue(isset($foo->bar));
}
}
-
-?>
diff --git a/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/Fixture/some_isset_to_property.php.inc b/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/Fixture/some_isset_to_property.php.inc
new file mode 100644
index 00000000..d2abcf40
--- /dev/null
+++ b/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/Fixture/some_isset_to_property.php.inc
@@ -0,0 +1,33 @@
+assertTrue(isset($object->someProperty));
+ }
+}
+
+?>
+-----
+assertObjectHasAttribute('someProperty', $object);
+ }
+}
+
+?>
diff --git a/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/config/configured_rule.php b/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/config/configured_rule.php
new file mode 100644
index 00000000..dae2a100
--- /dev/null
+++ b/rules-tests/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector/config/configured_rule.php
@@ -0,0 +1,9 @@
+withRules([AssertIssetToAssertObjectHasPropertyRector::class]);
diff --git a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture.php.inc b/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture.php.inc
deleted file mode 100644
index b00ea55a..00000000
--- a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture.php.inc
+++ /dev/null
@@ -1,33 +0,0 @@
-assertClassHasAttribute('property', 'stdClass');
- $this->assertClassNotHasAttribute('property', 'Namespaced\stdClass', 'message');
- }
-}
-
-?>
------
-assertTrue(property_exists(new \stdClass(), 'property'));
- $this->assertFalse(property_exists(new \Namespaced\stdClass(), 'property'), 'message');
- }
-}
-
-?>
diff --git a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture2.php.inc b/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture2.php.inc
deleted file mode 100644
index 2452c90b..00000000
--- a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture2.php.inc
+++ /dev/null
@@ -1,29 +0,0 @@
-assertObjectNotHasAttribute('property', $response);
- }
-}
-
-?>
------
-assertFalse(property_exists($response, 'property'));
- }
-}
-
-?>
diff --git a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture3.php.inc b/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture3.php.inc
deleted file mode 100644
index fcbd82da..00000000
--- a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture3.php.inc
+++ /dev/null
@@ -1,29 +0,0 @@
-assertObjectHasAttribute('property', $object->data);
- $this->assertObjectNotHasAttribute('property', $object->data);
- }
-}
-
-?>
------
-assertTrue(property_exists($object->data, 'property'));
- $this->assertFalse(property_exists($object->data, 'property'));
- }
-}
-
-?>
diff --git a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture4.php.inc b/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture4.php.inc
deleted file mode 100644
index d999cd39..00000000
--- a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/Fixture/fixture4.php.inc
+++ /dev/null
@@ -1,37 +0,0 @@
-assertObjectHasAttribute($property, $object);
- $this->assertObjectNotHasAttribute($property, $object);
- $this->assertObjectHasAttribute($property[0], $object);
- $this->assertObjectNotHasAttribute($property[1], $object);
- $this->assertObjectHasAttribute($property->name, $object);
- $this->assertObjectNotHasAttribute($property[1]->name, $object);
- }
-}
-
-?>
------
-assertTrue(property_exists($object, $property));
- $this->assertFalse(property_exists($object, $property));
- $this->assertTrue(property_exists($object, $property[0]));
- $this->assertFalse(property_exists($object, $property[1]));
- $this->assertTrue(property_exists($object, $property->name));
- $this->assertFalse(property_exists($object, $property[1]->name));
- }
-}
-
-?>
diff --git a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/config/configured_rule.php b/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/config/configured_rule.php
deleted file mode 100644
index b002cf4d..00000000
--- a/rules-tests/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector/config/configured_rule.php
+++ /dev/null
@@ -1,10 +0,0 @@
-rule(PropertyExistsWithoutAssertRector::class);
-};
diff --git a/rules/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector.php b/rules/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector.php
index 9fb8da35..01ca9077 100644
--- a/rules/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector.php
+++ b/rules/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector.php
@@ -5,20 +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\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
-use PhpParser\Node\Scalar\String_;
-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;
@@ -27,35 +21,20 @@
*/
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
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
- 'Turns isset comparisons to their method name alternatives in PHPUnit TestCase',
+ 'Turns assertTrue() + isset() comparisons to more precise assertArrayHasKey() method',
[
new CodeSample(
- '$this->assertTrue(isset($anything->foo));',
- '$this->assertObjectHasAttribute("foo", $anything);'
- ),
- new CodeSample(
- '$this->assertFalse(isset($anything["foo"]), "message");',
- '$this->assertArrayNotHasKey("foo", $anything, "message");'
+ '$this->assertTrue(isset($anything["foo"]), "message");',
+ '$this->assertArrayHasKey("foo", $anything, "message");'
),
]
);
@@ -74,7 +53,10 @@ 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;
}
@@ -82,89 +64,27 @@ public function refactor(Node $node): ?Node
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]::class;
- if (! in_array($variableNodeClass, [ArrayDimFetch::class, PropertyFetch::class], true)) {
- return null;
- }
-
- /** @var Isset_ $issetNode */
- $issetNode = $node->getArgs()[0]
-->value;
-
- $issetNodeArg = $issetNode->vars[0];
-
- if ($issetNodeArg instanceof PropertyFetch) {
- if ($this->hasMagicIsset($issetNodeArg->var)) {
- return null;
- }
-
- return $this->refactorPropertyFetchNode($node, $issetNodeArg);
- }
-
- if ($issetNodeArg instanceof ArrayDimFetch) {
- return $this->refactorArrayDimFetchNode($node, $issetNodeArg);
- }
-
- 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;
- }
-
- private function refactorPropertyFetchNode(MethodCall|StaticCall $node, PropertyFetch $propertyFetch): ?Node
- {
- $name = $this->getName($propertyFetch);
- if ($name === null) {
+ $issetVariable = $firstArgumentValue->vars[0];
+ if (! $issetVariable instanceof ArrayDimFetch) {
return null;
}
- $this->identifierManipulator->renameNodeWithMap($node, [
- self::ASSERT_TRUE => 'assertObjectHasAttribute',
- self::ASSERT_FALSE => 'assertObjectNotHasAttribute',
- ]);
-
- $oldArgs = $node->getArgs();
- unset($oldArgs[0]);
-
- $newArgs = $this->nodeFactory->createArgs([new String_($name), $propertyFetch->var]);
- $node->args = [...$newArgs, ...$oldArgs];
- return $node;
+ 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();
diff --git a/rules/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector.php b/rules/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector.php
new file mode 100644
index 00000000..e42e7a6c
--- /dev/null
+++ b/rules/PHPUnit100/Rector/MethodCall/AssertIssetToAssertObjectHasPropertyRector.php
@@ -0,0 +1,153 @@
+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>
+ */
+ 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 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;
+ }
+}
diff --git a/rules/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector.php b/rules/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector.php
deleted file mode 100644
index 4e194f11..00000000
--- a/rules/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector.php
+++ /dev/null
@@ -1,134 +0,0 @@
-
- */
- private const RENAME_METHODS_WITH_OBJECT_MAP = [
- 'assertObjectHasAttribute' => 'assertTrue',
- 'assertObjectNotHasAttribute' => 'assertFalse',
- ];
-
- /**
- * @var array
- */
- private const RENAME_METHODS_WITH_CLASS_MAP = [
- 'assertClassHasAttribute' => 'assertTrue',
- 'assertClassNotHasAttribute' => 'assertFalse',
- ];
-
- public function __construct(
- private readonly IdentifierManipulator $identifierManipulator,
- private readonly TestsNodeAnalyzer $testsNodeAnalyzer
- ) {
- }
-
- public function getRuleDefinition(): RuleDefinition
- {
- return new RuleDefinition(
- 'Turns PHPUnit TestCase assertObjectHasAttribute into `property_exists` comparisons',
- [
- new CodeSample(
- <<<'CODE_SAMPLE'
-$this->assertClassHasAttribute("property", "Class");
-$this->assertClassNotHasAttribute("property", "Class");
-CODE_SAMPLE
- ,
- <<<'CODE_SAMPLE'
-$this->assertFalse(property_exists(new Class, "property"));
-$this->assertTrue(property_exists(new Class, "property"));
-CODE_SAMPLE
- ),
- ]
- );
- }
-
- /**
- * @return array>
- */
- public function getNodeTypes(): array
- {
- return [MethodCall::class, StaticCall::class];
- }
-
- /**
- * @param MethodCall|StaticCall $node
- */
- public function refactor(Node $node): ?Node
- {
- if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, [
- 'assertClassHasAttribute',
- 'assertClassNotHasAttribute',
- 'assertObjectNotHasAttribute',
- 'assertObjectHasAttribute',
- ])) {
- return null;
- }
-
- $arguments = array_column($node->args, 'value');
- if (
- $arguments[0] instanceof String_ ||
- $arguments[0] instanceof Variable ||
- $arguments[0] instanceof ArrayDimFetch ||
- $arguments[0] instanceof PropertyFetch
- ) {
- $secondArg = $arguments[0];
- } else {
- return null;
- }
-
- if ($arguments[1] instanceof Variable) {
- $firstArg = new Variable($arguments[1]->name);
- $map = self::RENAME_METHODS_WITH_OBJECT_MAP;
- } elseif ($arguments[1] instanceof String_) {
- $firstArg = new New_(new FullyQualified($arguments[1]->value));
- $map = self::RENAME_METHODS_WITH_CLASS_MAP;
- } elseif ($arguments[1] instanceof PropertyFetch || $arguments[1] instanceof ArrayDimFetch) {
- $firstArg = $arguments[1];
- $map = self::RENAME_METHODS_WITH_OBJECT_MAP;
- } else {
- return null;
- }
-
- unset($node->args[0]);
- unset($node->args[1]);
-
- $propertyExistsFuncCall = new FuncCall(new Name('property_exists'), [
- new Arg($firstArg),
- new Arg($secondArg),
- ]);
-
- $newArgs = $this->nodeFactory->createArgs([$propertyExistsFuncCall]);
-
- $node->args = array_merge($newArgs, $node->getArgs());
- $this->identifierManipulator->renameNodeWithMap($node, $map);
-
- return $node;
- }
-}
diff --git a/src/Enum/AssertMethod.php b/src/Enum/AssertMethod.php
new file mode 100644
index 00000000..496d749d
--- /dev/null
+++ b/src/Enum/AssertMethod.php
@@ -0,0 +1,18 @@
+