diff --git a/build/target-repository/docs/rector_rules_overview.md b/build/target-repository/docs/rector_rules_overview.md index e41ae0f3965..b8550568d8e 100644 --- a/build/target-repository/docs/rector_rules_overview.md +++ b/build/target-repository/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# 352 Rules Overview +# 353 Rules Overview
@@ -10,7 +10,7 @@ - [CodingStyle](#codingstyle) (27) -- [DeadCode](#deadcode) (42) +- [DeadCode](#deadcode) (43) - [EarlyReturn](#earlyreturn) (9) @@ -2509,6 +2509,27 @@ Remove initialization with null value from property declarations
+### RemoveNullTagValueNodeRector + +Remove `@var/@param/@return` null docblock + +- class: [`Rector\DeadCode\Rector\ClassMethod\RemoveNullTagValueNodeRector`](../rules/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector.php) + +```diff + class SomeClass + { +- /** +- * @return null +- */ + public function foo() + { + return null; + } + } +``` + +
+ ### RemoveParentCallWithoutParentRector Remove unused parent call with no parent class diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_param.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_param.php.inc new file mode 100644 index 00000000000..ca9d474d56a --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_param.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_return.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_return.php.inc new file mode 100644 index 00000000000..5279eb21411 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_return.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_var.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_var.php.inc new file mode 100644 index 00000000000..f681f440623 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_var.php.inc @@ -0,0 +1,24 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_var2.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_var2.php.inc new file mode 100644 index 00000000000..b92bfa15a05 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/remove_null_var2.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/skip_different_tag_value.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/skip_different_tag_value.php.inc new file mode 100644 index 00000000000..1c3e49073c8 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/Fixture/skip_different_tag_value.php.inc @@ -0,0 +1,14 @@ +doTestFile($filePath); + } + + /** + * @return Iterator + */ + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/config/configured_rule.php b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/config/configured_rule.php new file mode 100644 index 00000000000..9f0870d9adc --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(RemoveNullTagValueNodeRector::class); +}; diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector.php new file mode 100644 index 00000000000..242a68f5a5b --- /dev/null +++ b/rules/DeadCode/Rector/ClassMethod/RemoveNullTagValueNodeRector.php @@ -0,0 +1,170 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class, Expression::class, Property::class]; + } + + private function isNull(VarTagValueNode|ParamTagValueNode|ReturnTagValueNode $tag): bool + { + return $tag->type instanceof IdentifierTypeNode + && $tag->type->__toString() === 'null' + && $tag->description === ''; + } + + /** + * @param string[] $paramNames + */ + private function removeParamNullTag(PhpDocInfo $phpDocInfo, array $paramNames): void + { + $phpDocNodeTraverser = new PhpDocNodeTraverser(); + $phpDocNodeTraverser->traverseWithCallable( + $phpDocInfo->getPhpDocNode(), + '', + static function (AstNode $astNode) use ($paramNames) : ?int { + if (! $astNode instanceof PhpDocTagNode) { + return null; + } + + if (! $astNode->value instanceof ParamTagValueNode) { + return null; + } + + if (in_array($astNode->value->parameterName , $paramNames, true)) { + return PhpDocNodeTraverser::NODE_REMOVE; + } + + return null; + }); + } + + private function processVarTagNull(Expression|Property $node): ?Node + { + $phpdocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + $varTagValueNode = $phpdocInfo->getVarTagValueNode(); + + if ($varTagValueNode instanceof VarTagValueNode && $this->isNull($varTagValueNode)) { + + $phpdocInfo->removeByType(VarTagValueNode::class); + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } + + return null; + } + + /** + * @param ClassMethod|Function_|Expression|Property $node + */ + public function refactor(Node $node): ?Node + { + if ($node instanceof Expression || $node instanceof Property) { + return $this->processVarTagNull($node); + } + + $phpdocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + $removedParamNames = []; + + foreach ($node->params as $param) { + $paramName = $this->getName($param); + $paramTagValueNode = $phpdocInfo->getParamTagValueByName($paramName); + + if ($paramTagValueNode instanceof ParamTagValueNode && $this->isNull($paramTagValueNode)) { + $removedParamNames[] = $paramTagValueNode->parameterName; + } + } + + $hasRemoved = false; + if ($removedParamNames !== []) { + $this->removeParamNullTag($phpdocInfo, $removedParamNames); + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + $hasRemoved = true; + } + + $returnTagValueNode = $phpdocInfo->getReturnTagValue(); + if ($returnTagValueNode instanceof ReturnTagValueNode && $this->isNull($returnTagValueNode)) { + $phpdocInfo->removeByType(ReturnTagValueNode::class); + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + $hasRemoved = true; + } + + if (! $hasRemoved) { + return null; + } + + return $node; + } +} diff --git a/utils/Command/MissingInSetCommand.php b/utils/Command/MissingInSetCommand.php index 659e5ed2ecb..5be75d5d8d7 100644 --- a/utils/Command/MissingInSetCommand.php +++ b/utils/Command/MissingInSetCommand.php @@ -6,6 +6,7 @@ use Nette\Utils\Strings; use Rector\Core\Contract\Rector\ConfigurableRectorInterface; +use Rector\DeadCode\Rector\ClassMethod\RemoveNullTagValueNodeRector; use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; use Rector\Privatization\Rector\Class_\FinalizeClassesWithoutChildrenCollectorRector; use Rector\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector; @@ -34,6 +35,8 @@ final class MissingInSetCommand extends Command FinalizeClassesWithoutChildrenCollectorRector::class, // changes behavior, should be applied on purpose regardless PHP 7.3 level JsonThrowOnErrorRector::class, + // in confront with sub type safe belt detection on RemoveUseless*TagRector + RemoveNullTagValueNodeRector::class, ]; public function __construct(