From 3e7336d28bcea367e2f7100a5547773551483f14 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 25 Aug 2026 16:36:23 +0100 Subject: [PATCH] [CodeQuality] Fix AddInstanceofAssertForNullableArgumentRector duplicate assertInstanceOf on every run inside traits --- .../Fixture/nullable_arg_in_trait.php.inc | 40 ++++++++++++++++ .../skip_already_asserted_in_trait.php.inc | 18 +++++++ .../Source/SomeFactory.php | 17 +++++++ ...tanceofAssertForNullableArgumentRector.php | 47 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Fixture/nullable_arg_in_trait.php.inc create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Fixture/skip_already_asserted_in_trait.php.inc create mode 100644 rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Source/SomeFactory.php diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Fixture/nullable_arg_in_trait.php.inc b/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Fixture/nullable_arg_in_trait.php.inc new file mode 100644 index 00000000..c8d979dc --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Fixture/nullable_arg_in_trait.php.inc @@ -0,0 +1,40 @@ +create(); + + $someFactory->process($someObject); + } +} + +?> +----- +create(); + $this->assertInstanceOf(\Rector\PHPUnit\Tests\CodeQuality\Rector\ClassMethod\AddInstanceofAssertForNullableArgumentRector\Source\SomeClassUsedInTests::class, $someObject); + + $someFactory->process($someObject); + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Fixture/skip_already_asserted_in_trait.php.inc b/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Fixture/skip_already_asserted_in_trait.php.inc new file mode 100644 index 00000000..e0114fee --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Fixture/skip_already_asserted_in_trait.php.inc @@ -0,0 +1,18 @@ +create(); + $this->assertInstanceOf(\Rector\PHPUnit\Tests\CodeQuality\Rector\ClassMethod\AddInstanceofAssertForNullableArgumentRector\Source\SomeClassUsedInTests::class, $someObject); + + $someFactory->process($someObject); + } +} diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Source/SomeFactory.php b/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Source/SomeFactory.php new file mode 100644 index 00000000..cf26923f --- /dev/null +++ b/rules-tests/CodeQuality/Rector/ClassMethod/AddInstanceofAssertForNullableArgumentRector/Source/SomeFactory.php @@ -0,0 +1,17 @@ +stmts as $key => $stmt) { + // already asserted in a previous run? drop the variable to keep the rule idempotent + $assertedVariableName = $this->matchAssertInstanceOfVariableName($stmt); + if (is_string($assertedVariableName)) { + $alreadyAssertedVariableNameToType = $variableNameToTypeCollection->matchByVariableName( + $assertedVariableName + ); + + if ($alreadyAssertedVariableNameToType instanceof VariableNameToType) { + $variableNameToTypeCollection->remove($alreadyAssertedVariableNameToType); + } + + continue; + } + // has callable on nullable variable of already collected name? $matchedNullableVariableNameToType = $this->matchedNullableArgumentNameToType( $stmt, @@ -224,4 +239,36 @@ private function matchedNullableArgumentNameToType( return $matchedNullableVariableNameToType; } + + private function matchAssertInstanceOfVariableName(Stmt $stmt): ?string + { + if (! $stmt instanceof Expression) { + return null; + } + + if (! $stmt->expr instanceof MethodCall) { + return null; + } + + $methodCall = $stmt->expr; + if (! $this->isName($methodCall->name, 'assertInstanceOf')) { + return null; + } + + $args = $methodCall->getArgs(); + if (! isset($args[1])) { + return null; + } + + if (! $args[1]->value instanceof Variable) { + return null; + } + + $variableName = $this->getName($args[1]->value); + if (! is_string($variableName)) { + return null; + } + + return $variableName; + } }